DataDog / dd-trace-py

Datadog Python APM Client
https://ddtrace.readthedocs.io/
Other
552 stars 414 forks source link

Method object disappears when it's mocked out by unittest.mock and patched by ddtrace #4946

Closed ento closed 6 months ago

ento commented 1 year ago

Summary of problem

I encountered this issue when trying to instrument a pytest test suite for CI visibility. There's a test case that mocks out the POST handler of a Django view class to confirm that it was called, and it started failing when I invoked the test suite with --ddtrace --ddtrace-patch-all. Specifying --ddtrace alone did not result in a test failure. As you can see below, I was able to narrow it down to a reproducible example that doesn't involve pytest or Django.

I can likely fix the test by not relying on mocking (which is better anyways in this case), but wanted to report the issue in case this is something unexpected and should be fixed on dd-trace-py's end.

Which version of dd-trace-py are you using?

1.7.2 (the version used by the repl.it project below)

Which version of pip are you using?

21.2.dev0 (the version used by the repl.it project below)

Which libraries and their versions are you using?

`poetry run pip freeze` (what the repl.it project below outputs) ``` aiohttp @ https://files.pythonhosted.org/packages/f0/02/071500ac4da91f762dc35c9e22438b73158077da4e851a8e4741fa05ab4a/aiohttp-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b aiosignal @ https://files.pythonhosted.org/packages/76/ac/a7305707cb852b7e16ff80eaf5692309bde30e2b1100a1fcacdc8f731d97/aiosignal-1.3.1-py3-none-any.whl#sha256=f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17 argon2-cffi @ https://files.pythonhosted.org/packages/a8/07/946d5a9431bae05a776a59746ec385fbb79b526738d25e4202d3e0bbf7f4/argon2_cffi-21.3.0-py3-none-any.whl#sha256=8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80 argon2-cffi-bindings @ https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae asgiref @ https://files.pythonhosted.org/packages/8f/29/38d10a47b322a77b2d12c2b79c789f52956f733cb701d4d5157c76b5f238/asgiref-3.6.0-py3-none-any.whl#sha256=71e68008da809b957b7ee4b43dbccff33d1b23519fb8344e33f049897077afac async-timeout @ https://files.pythonhosted.org/packages/d6/c1/8991e7c5385b897b8c020cdaad718c5b087a6626d1d11a23e1ea87e325a7/async_timeout-4.0.2-py3-none-any.whl#sha256=8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c attrs @ https://files.pythonhosted.org/packages/fb/6e/6f83bf616d2becdf333a1640f1d463fef3150e2e926b7010cb0f81c95e88/attrs-22.2.0-py3-none-any.whl#sha256=29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836 bytecode @ https://files.pythonhosted.org/packages/08/c6/ed08b13e47597166bc6402072a72d3683bd1fd9e2da8fab59a982377d154/bytecode-0.14.0-py3-none-any.whl#sha256=f7b7cbed3239acee036d6c0f9d04286b100921114601bf844ae569b95bf91a9f CacheControl==0.12.11 cachy==0.3.0 cattrs @ https://files.pythonhosted.org/packages/43/3b/1d34fc4449174dfd2bc5ad7047a23edb6558b2e4b5a41b25a8ad6655c6c7/cattrs-22.2.0-py3-none-any.whl#sha256=bc12b1f0d000b9f9bee83335887d532a1d3e99a833d1bf0882151c97d3e68c21 certifi @ https://files.pythonhosted.org/packages/71/4c/3db2b8021bd6f2f0ceb0e088d6b2d49147671f25832fb17970e9b583d742/certifi-2022.12.7-py3-none-any.whl#sha256=4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18 cffi==1.15.1 charset-normalizer @ https://files.pythonhosted.org/packages/db/51/a507c856293ab05cdc1db77ff4bc1268ddd39f29e7dc4919aa497f0adbec/charset_normalizer-2.1.1-py3-none-any.whl#sha256=83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f cleo==0.8.1 click @ https://files.pythonhosted.org/packages/c2/f1/df59e28c642d583f7dacffb1e0965d0e00b218e0186d7858ac5233dce840/click-8.1.3-py3-none-any.whl#sha256=bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48 clikit==0.6.2 crashtest==0.3.1 cryptography @ https://files.pythonhosted.org/packages/26/f8/a81170a816679fca9ccd907b801992acfc03c33f952440421c921af2cc57/cryptography-38.0.4-cp36-abi3-manylinux_2_28_x86_64.whl#sha256=ce127dd0a6a0811c251a6cddd014d292728484e530d80e872ad9806cfb1c5b3c ddsketch @ https://files.pythonhosted.org/packages/90/fe/d2fe499cb71d2355d973b35795311fde250f9c36668602e91ae31a596e72/ddsketch-2.0.4-py3-none-any.whl#sha256=3227a270fd686a29d3a7128f9352ccf852314410380fc11384356f1ae2a75938 ddtrace @ https://files.pythonhosted.org/packages/8f/3a/8f479a97a5bd89d3c3cf84c29f61c0ccd64537bec9dd9d3342fd75c4e51e/ddtrace-1.7.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=1d6017821fc7ea8198b710896555135b6583899cc9f15179e1e96073be59b880 debugpy @ https://files.pythonhosted.org/packages/92/d5/b0f9b31b5fc902aa1611d0163652713b21661201f7008051fd5e512e851b/debugpy-1.6.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=17039e392d6f38388a68bd02c5f823b32a92142a851e96ba3ec52aeb1ce9d900 distlib==0.3.6 Django @ https://files.pythonhosted.org/packages/2d/ac/9f013a51e6008ba94a282c15778a3ea51a0953f6711a77f9baa471fd1b1d/Django-4.1.5-py3-none-any.whl#sha256=4b214a05fe4c99476e99e2445c8b978c8369c18d4dea8e22ec412862715ad763 envier @ https://files.pythonhosted.org/packages/6a/98/f444573540319ed549ea39986d935412a18228c323a11e07f2dd2fda549b/envier-0.4.0-py3-none-any.whl#sha256=7b91af0f16ea3e56d91ec082f038987e81b441fc19c657a8b8afe0909740a706 exceptiongroup @ https://files.pythonhosted.org/packages/e8/14/9c6a7e5f12294ccd6975a45e02899ed25468cd7c2c86f3d9725f387f9f5f/exceptiongroup-1.1.0-py3-none-any.whl#sha256=327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e filelock==3.9.0 Flask @ https://files.pythonhosted.org/packages/0f/43/15f4f9ab225b0b25352412e8daa3d0e3d135fcf5e127070c74c3632c8b4c/Flask-2.2.2-py3-none-any.whl#sha256=b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526 frozenlist @ https://files.pythonhosted.org/packages/49/0e/c57ad9178618cf81be0fbb8430f17cf05423403143819d3631c7c09744c2/frozenlist-1.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf html5lib==1.1 idna==3.4 iso8601 @ https://files.pythonhosted.org/packages/65/6c/9d72435c72adfa6e4ed1824b6df7fffbeaaf15c653881e9b041a318ba572/iso8601-1.1.0-py3-none-any.whl#sha256=8400e90141bf792bce2634df533dc57e3bee19ea120a87bebcd3da89a58ad73f itsdangerous @ https://files.pythonhosted.org/packages/68/5f/447e04e828f47465eeab35b5d408b7ebaaaee207f48b7136c5a7267a30ae/itsdangerous-2.1.2-py3-none-any.whl#sha256=2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 jedi @ https://files.pythonhosted.org/packages/6d/60/4acda63286ef6023515eb914543ba36496b8929cb7af49ecce63afde09c6/jedi-0.18.2-py2.py3-none-any.whl#sha256=203c1fd9d969ab8f2119ec0a3342e0b49910045abe6af0a3ae83a5764d54639e jeepney==0.8.0 Jinja2 @ https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl#sha256=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 jsonschema @ https://files.pythonhosted.org/packages/c1/97/c698bd9350f307daad79dd740806e1a59becd693bd11443a0f531e3229b3/jsonschema-4.17.3-py3-none-any.whl#sha256=a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6 keyring==21.8.0 lockfile==0.12.2 MarkupSafe @ https://files.pythonhosted.org/packages/3d/66/2f636ba803fd6eb4cee7b3106ae02538d1e84a7fb7f4f8775c6528a87d31/MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323 msgpack==1.0.4 multidict @ https://files.pythonhosted.org/packages/56/b5/ac112889bfc68e6cf4eda1e4325789b166c51c6cd29d5633e28fb2c2f966/multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93 packaging @ https://files.pythonhosted.org/packages/ed/35/a31aed2993e398f6b09a790a181a7927eb14610ee8bbf02dc14d31677f1c/packaging-23.0-py3-none-any.whl#sha256=714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2 parso @ https://files.pythonhosted.org/packages/05/63/8011bd08a4111858f79d2b09aad86638490d62fbf881c44e434a6dfca87b/parso-0.8.3-py2.py3-none-any.whl#sha256=c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75 passlib @ https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl#sha256=aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1 pastel==0.2.1 pexpect==4.8.0 pkginfo==1.9.6 platformdirs==2.6.2 pluggy @ https://files.pythonhosted.org/packages/9e/01/f38e2ff29715251cf25532b9082a1589ab7e4f571ced434f98d0139336dc/pluggy-1.0.0-py2.py3-none-any.whl#sha256=74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3 poetry==1.1.11 poetry-core==1.0.8 protobuf @ https://files.pythonhosted.org/packages/e7/a2/3273c05fc5d959fa90de6453ebd6d45c6d4fab3ec212d631625ea5780921/protobuf-4.21.12-cp37-abi3-manylinux2014_x86_64.whl#sha256=78a28c9fa223998472886c77042e9b9afb6fe4242bd2a2a5aced88e3f4422aa7 ptyprocess==0.7.0 pycparser==2.21 pycryptodomex @ https://files.pythonhosted.org/packages/76/dd/7276f37251f84931bd97bb42fe10455cad782dcb9a38b9820f65d2a098e8/pycryptodomex-3.16.0-cp35-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl#sha256=70288d9bfe16b2fd0d20b6c365db614428f1bcde7b20d56e74cf88ade905d9eb pyflakes @ https://files.pythonhosted.org/packages/dc/13/63178f59f74e53acc2165aee4b002619a3cfa7eeaeac989a9eb41edf364e/pyflakes-2.5.0-py2.py3-none-any.whl#sha256=4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2 pylev==1.4.0 pyparsing==3.0.9 pyrsistent @ https://files.pythonhosted.org/packages/64/de/375aa14daaee107f987da76ca32f7a907fea00fa8b8afb67dc09bec0de91/pyrsistent-0.19.3-py3-none-any.whl#sha256=ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64 pyseto @ https://files.pythonhosted.org/packages/3b/60/3f03f7a65fb445cbaa3cde09f05ed3095af524091fd728c4dfbd1320fbcf/pyseto-1.7.0-py3-none-any.whl#sha256=031d443a87d589db56df090a9ad9a53466d0a2f03c185ef89c38a4197569a256 python-lsp-jsonrpc @ https://files.pythonhosted.org/packages/06/ee/754bfd5f6bfe7162c10d3ecb0aeef6f882f91d3231596c83f761a75efd0b/python_lsp_jsonrpc-1.0.0-py3-none-any.whl#sha256=079b143be64b0a378bdb21dff5e28a8c1393fe7e8a654ef068322d754e545fc7 pytoolconfig @ https://files.pythonhosted.org/packages/4d/1e/49b73fb59354fb0c3f7afad39c58dd47f5f0dbed2ffb1977dee3295db16c/pytoolconfig-1.2.4-py3-none-any.whl#sha256=7befe396f91b2593345b829a7745c7f459f04fc6c53fc86c0b771945446a7bd1 replit @ https://files.pythonhosted.org/packages/87/2c/23c810fa4a299f7d715a26f400b6ee6e618f0679d9a93159f7feb3c14e2b/replit-3.2.5-py3-none-any.whl#sha256=c8ca833203e5e56b53d594523fec204bc31cb1ae4b9b703778e4b8edda292313 replit-python-lsp-server @ https://files.pythonhosted.org/packages/09/42/44ff903505cefa2b2d06fcc164ddff74510eece259e30da6a0c2c68976c8/replit_python_lsp_server-1.15.9-py3-none-any.whl#sha256=205faf150008be7d3da2ac16fc178c833d22835e24841cb82b666443a6b28bef requests @ https://files.pythonhosted.org/packages/d2/f4/274d1dbe96b41cf4e0efb70cbced278ffd61b5c7bb70338b62af94ccb25b/requests-2.28.2-py3-none-any.whl#sha256=64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa requests-toolbelt==0.9.1 rope @ https://files.pythonhosted.org/packages/bd/fb/8ec020125efe0c529719ba200e3fc97f79f00316a1c1d1312925243fe5cc/rope-1.7.0-py3-none-any.whl#sha256=893dd80ba7077fc9f6f42b0a849372076b70f1d6e405b9f0cc52781ffa0e6890 SecretStorage==3.3.3 shellingham==1.5.0.post1 six==1.16.0 sqlparse @ https://files.pythonhosted.org/packages/97/d3/31dd2c3e48fc2060819f4acb0686248250a0f2326356306b38a42e059144/sqlparse-0.4.3-py3-none-any.whl#sha256=0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34 tenacity @ https://files.pythonhosted.org/packages/a5/94/933ce16d18450ccf518a6da5bd51418611e8776b992070b9f40b2f9cedff/tenacity-8.1.0-py3-none-any.whl#sha256=35525cd47f82830069f0d6b73f7eb83bc5b73ee2fff0437952cedf98b27653ac toml @ https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl#sha256=806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b tomli @ https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl#sha256=939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc tomlkit==0.11.6 typing-extensions @ https://files.pythonhosted.org/packages/74/60/18783336cc7fcdd95dae91d73477830aa53f5d3181ae4fe20491d7fc3199/typing_extensions-3.10.0.2-py3-none-any.whl#sha256=f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34 ujson @ https://files.pythonhosted.org/packages/23/46/874217a97b822d0d9c072fe49db362ce1ece8e912ea6422a3f12fa5e56e1/ujson-5.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=54384ce4920a6d35fa9ea8e580bc6d359e3eb961fa7e43f46c78e3ed162d56ff urllib3 @ https://files.pythonhosted.org/packages/fe/ca/466766e20b767ddb9b951202542310cba37ea5f2d792dae7589f1741af58/urllib3-1.26.14-py2.py3-none-any.whl#sha256=75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1 virtualenv==20.17.1 webencodings==0.5.1 Werkzeug @ https://files.pythonhosted.org/packages/c8/27/be6ddbcf60115305205de79c29004a0c6bc53cec814f733467b1bb89386d/Werkzeug-2.2.2-py3-none-any.whl#sha256=f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5 whatthepatch @ https://files.pythonhosted.org/packages/93/f7/3276ba8d661f459afb171af169a7f68a390ede84c5510baf4575ce3316df/whatthepatch-1.0.3-py3-none-any.whl#sha256=465e075f612bc145905cf2574ea1c5a0122b9eb41959408d676b3b929eae0d8e xmltodict @ https://files.pythonhosted.org/packages/94/db/fd0326e331726f07ff7f40675cd86aa804bfd2e5016c727fa761c934990e/xmltodict-0.13.0-py2.py3-none-any.whl#sha256=aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852 yapf @ https://files.pythonhosted.org/packages/47/88/843c2e68f18a5879b4fbf37cb99fbabe1ffc4343b2e63191c8462235c008/yapf-0.32.0-py2.py3-none-any.whl#sha256=8fea849025584e486fd06d6ba2bed717f396080fd3cc236ba10cb97c4c51cf32 yarl @ https://files.pythonhosted.org/packages/b5/e0/6ea3832faed10de6a06cd407c660e6978d5538fe7489e934fb9967c8bb8b/yarl-1.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=6c4fcfa71e2c6a3cb568cf81aadc12768b9995323186a10827beccf5fa23d4f8 ```

How can we reproduce your problem?

MCVE: https://replit.com/@ento/FlawedHarmfulOperation

This has three examples:

  1. Invoking a mocked out Django view without tracing: succeeds
  2. Invoking a mocked out Django view with tracing: fails
  3. Invoking a simple method after mocking it out and setting up tracing: fails

The third example is what's essentially happening with the second example: when Django's default request dispatcher tries to get the correct handler method for the current request's HTTP verb, it can't find MockedView.get and instead returns http_method_not_allowed.

main.py ```py import os from unittest import mock os.environ["AWS_LAMBDA_FUNCTION_NAME"] = "forcing_ddtrace_to_use_log_writer" import django from ddtrace import Pin, tracer from ddtrace.contrib import trace_utils from ddtrace.contrib.django.patch import traced_as_view, traced_func from django.conf import settings from django.http import HttpResponse from django.test import RequestFactory from django.views import View rf = RequestFactory() try: settings.configure() except RuntimeError: pass class MockedView(View): def get(self, request): return HttpResponse("hello") def request_get(): with mock.patch.object(MockedView, "get") as mock_get: view = MockedView.as_view() request = rf.get("/") response = view(request) try: assert mock_get.called, "MockedView.get() should've been called" print("o", "got", response) except AssertionError: print("! mocked 'get' method was not called") print("###", "before patching", "###") request_get() print("") Pin().onto(django) trace_utils.wrap(django, "views.generic.base.View.as_view", traced_as_view(django)) print("###", "after patching", "###") request_get() print("") print("###", "raw object", "###") class A: def foo(): print("hello foo") print("before patching: getattr(A(), 'foo', None) =", getattr(A(), "foo", None)) with mock.patch.object(A, "foo") as mock_foo: print("after patching: getattr(A(), 'foo', None) =", getattr(A(), "foo", None)) trace_utils.wrap(A, "foo", traced_func(django, name="foo", resource="A")) print("after wrapping: getattr(A(), 'foo', None) =", getattr(A(), "foo", None)) A().foo() print("mock_foo.called", mock_foo.called) ```

What is the result that you get?

### before patching ###
o got <MagicMock name='get()' id='140623340048208'>

### after patching ###
Method Not Allowed (GET): /
{"traces": [ ELIDED FOR BREVITY ]}
! mocked 'get' method was not called

### raw object ###
before patching: getattr(A(), 'foo', None) = <bound method A.foo of <__main__.A object at 0x7fe56bec8b10>>
after patching: getattr(A(), 'foo', None) = <MagicMock name='foo' id='140623334876944'>
after wrapping: getattr(A(), 'foo', None) = None
Traceback (most recent call last):
  File "mocked_view.py", line 67, in <module>
    A().foo()
AttributeError: 'MagicMock' object has no attribute '__get__'

What is the result that you expected?

### before patching ###
o got <MagicMock name='get()' id='139930083200720'>

### after patching ###
{"traces": [ ELIDED FOR BREVITY ]}
o got <MagicMock name='get()' id='139930078059280'>

### raw object ###
before patching: getattr(A(), 'foo', None) = <bound method A.foo of <__main__.A object at 0x7f44029899d0>>
after patching: getattr(A(), 'foo', None) = <MagicMock name='foo' id='139930078097488'>
after wrapping: getattr(A(), 'foo', None) = <MagicMock name='foo' id='139930078097488'>
mock_foo.called True
emmettbutler commented 1 year ago

Apologies for the lack of response on this ticket, our team missed it for a long time. Is this issue reproducible on ddtrace 2.1.0?

ento commented 11 months ago

Yes, confirmed that it still happens with v1.20.14 and v2.3.1.

v1.20.14

https://replit.com/@ento/FlawedHarmfulOperation ![image](https://github.com/DataDog/dd-trace-py/assets/21108/de061890-0524-47cf-9ad7-fb1f198d23e1)

v2.3.1

https://replit.com/@ento/FlawedHarmfulOperation-ddtracev2 ![image](https://github.com/DataDog/dd-trace-py/assets/21108/d83b5f7e-4576-498d-a52f-9fed6771df7b)

github-actions[bot] commented 6 months ago

This issue has been automatically closed after a period of inactivity. If it's a feature request, it has been added to the maintainers' internal backlog and will be included in an upcoming round of feature prioritization. Please comment or reopen if you think this issue was closed in error.