stdlib.py 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
  4. """Checkers for various standard library functions."""
  5. from __future__ import annotations
  6. import sys
  7. from collections.abc import Iterable
  8. from typing import TYPE_CHECKING, Any
  9. import astroid
  10. from astroid import nodes, util
  11. from astroid.typing import InferenceResult
  12. from pylint import interfaces
  13. from pylint.checkers import BaseChecker, DeprecatedMixin, utils
  14. from pylint.interfaces import HIGH, INFERENCE
  15. from pylint.typing import MessageDefinitionTuple
  16. if TYPE_CHECKING:
  17. from pylint.lint import PyLinter
  18. DeprecationDict = dict[tuple[int, int, int], set[str]]
  19. OPEN_FILES_MODE = ("open", "file")
  20. OPEN_FILES_FUNCS = (*OPEN_FILES_MODE, "read_text", "write_text")
  21. UNITTEST_CASE = "unittest.case"
  22. THREADING_THREAD = "threading.Thread"
  23. COPY_COPY = "copy.copy"
  24. OS_ENVIRON = "os._Environ"
  25. ENV_GETTERS = ("os.getenv",)
  26. SUBPROCESS_POPEN = "subprocess.Popen"
  27. SUBPROCESS_RUN = "subprocess.run"
  28. OPEN_MODULE = {"_io", "pathlib", "pathlib._local"}
  29. PATHLIB_MODULE = {"pathlib", "pathlib._local"}
  30. DEBUG_BREAKPOINTS = ("builtins.breakpoint", "sys.breakpointhook", "pdb.set_trace")
  31. LRU_CACHE = {
  32. "functools.lru_cache", # Inferred for @lru_cache
  33. "functools._lru_cache_wrapper.wrapper", # Inferred for @lru_cache() on >= Python 3.8
  34. "functools.lru_cache.decorating_function", # Inferred for @lru_cache() on <= Python 3.7
  35. }
  36. NON_INSTANCE_METHODS = {"builtins.staticmethod", "builtins.classmethod"}
  37. # For modules, see ImportsChecker
  38. DEPRECATED_ARGUMENTS: dict[
  39. tuple[int, int, int], dict[str, tuple[tuple[int | None, str], ...]]
  40. ] = {
  41. (0, 0, 0): {
  42. "int": ((None, "x"),),
  43. "bool": ((None, "x"),),
  44. "float": ((None, "x"),),
  45. },
  46. (3, 5, 0): {
  47. "importlib._bootstrap_external.cache_from_source": ((1, "debug_override"),),
  48. },
  49. (3, 8, 0): {
  50. "asyncio.tasks.sleep": ((None, "loop"),),
  51. "asyncio.tasks.gather": ((None, "loop"),),
  52. "asyncio.tasks.shield": ((None, "loop"),),
  53. "asyncio.tasks.wait_for": ((None, "loop"),),
  54. "asyncio.tasks.wait": ((None, "loop"),),
  55. "asyncio.tasks.as_completed": ((None, "loop"),),
  56. "asyncio.subprocess.create_subprocess_exec": ((None, "loop"),),
  57. "asyncio.subprocess.create_subprocess_shell": ((4, "loop"),),
  58. "gettext.translation": ((5, "codeset"),),
  59. "gettext.install": ((2, "codeset"),),
  60. "functools.partialmethod": ((None, "func"),),
  61. "weakref.finalize": ((None, "func"), (None, "obj")),
  62. "profile.Profile.runcall": ((None, "func"),),
  63. "cProfile.Profile.runcall": ((None, "func"),),
  64. "bdb.Bdb.runcall": ((None, "func"),),
  65. "trace.Trace.runfunc": ((None, "func"),),
  66. "curses.wrapper": ((None, "func"),),
  67. "unittest.case.TestCase.addCleanup": ((None, "function"),),
  68. "concurrent.futures.thread.ThreadPoolExecutor.submit": ((None, "fn"),),
  69. "concurrent.futures.process.ProcessPoolExecutor.submit": ((None, "fn"),),
  70. "contextlib._BaseExitStack.callback": ((None, "callback"),),
  71. "contextlib.AsyncExitStack.push_async_callback": ((None, "callback"),),
  72. "multiprocessing.managers.Server.create": ((None, "c"), (None, "typeid")),
  73. "multiprocessing.managers.SharedMemoryServer.create": (
  74. (None, "c"),
  75. (None, "typeid"),
  76. ),
  77. },
  78. (3, 9, 0): {"random.Random.shuffle": ((1, "random"),)},
  79. (3, 12, 0): {
  80. "argparse.BooleanOptionalAction": ((3, "type"), (4, "choices"), (7, "metavar")),
  81. "coroutine.throw": ((1, "value"), (2, "traceback")),
  82. "email.utils.localtime": ((1, "isdst"),),
  83. "shutil.rmtree": ((2, "onerror"),),
  84. "sysconfig.is_python_build": ((0, "check_home"),),
  85. },
  86. (3, 13, 0): {
  87. "dis.get_instructions": ((2, "show_caches"),),
  88. },
  89. (3, 14, 0): {
  90. "argparse.ArgumentParser.add_argument_group": ((None, "prefix_chars"),),
  91. "threading.RLock": ((0, "x"),),
  92. },
  93. }
  94. DEPRECATED_DECORATORS: DeprecationDict = {
  95. (3, 8, 0): {"asyncio.coroutine"},
  96. (3, 3, 0): {
  97. "abc.abstractclassmethod",
  98. "abc.abstractstaticmethod",
  99. "abc.abstractproperty",
  100. },
  101. (3, 4, 0): {"importlib.util.module_for_loader"},
  102. (3, 13, 0): {"typing.no_type_check_decorator"},
  103. }
  104. DEPRECATED_METHODS: dict[int, DeprecationDict] = {
  105. 0: {
  106. (0, 0, 0): {
  107. "cgi.parse_qs",
  108. "cgi.parse_qsl",
  109. "ctypes.c_buffer",
  110. "distutils.command.register.register.check_metadata",
  111. "distutils.command.sdist.sdist.check_metadata",
  112. "tkinter.Misc.tk_menuBar",
  113. "tkinter.Menu.tk_bindForTraversal",
  114. }
  115. },
  116. 2: {
  117. (2, 6, 0): {
  118. "commands.getstatus",
  119. "os.popen2",
  120. "os.popen3",
  121. "os.popen4",
  122. "macostools.touched",
  123. },
  124. (2, 7, 0): {
  125. "unittest.case.TestCase.assertEquals",
  126. "unittest.case.TestCase.assertNotEquals",
  127. "unittest.case.TestCase.assertAlmostEquals",
  128. "unittest.case.TestCase.assertNotAlmostEquals",
  129. "unittest.case.TestCase.assert_",
  130. "xml.etree.ElementTree.Element.getchildren",
  131. "xml.etree.ElementTree.Element.getiterator",
  132. "xml.etree.ElementTree.XMLParser.getiterator",
  133. "xml.etree.ElementTree.XMLParser.doctype",
  134. },
  135. },
  136. 3: {
  137. (3, 0, 0): {
  138. "inspect.getargspec",
  139. "failUnlessEqual",
  140. "assertEquals",
  141. "failIfEqual",
  142. "assertNotEquals",
  143. "failUnlessAlmostEqual",
  144. "assertAlmostEquals",
  145. "failIfAlmostEqual",
  146. "assertNotAlmostEquals",
  147. "failUnless",
  148. "assert_",
  149. "failUnlessRaises",
  150. "failIf",
  151. "assertRaisesRegexp",
  152. "assertRegexpMatches",
  153. "assertNotRegexpMatches",
  154. },
  155. (3, 1, 0): {
  156. "base64.encodestring",
  157. "base64.decodestring",
  158. "ntpath.splitunc",
  159. "os.path.splitunc",
  160. "os.stat_float_times",
  161. "turtle.RawTurtle.settiltangle",
  162. },
  163. (3, 2, 0): {
  164. "cgi.escape",
  165. "configparser.RawConfigParser.readfp",
  166. "xml.etree.ElementTree.Element.getchildren",
  167. "xml.etree.ElementTree.Element.getiterator",
  168. "xml.etree.ElementTree.XMLParser.getiterator",
  169. "xml.etree.ElementTree.XMLParser.doctype",
  170. },
  171. (3, 3, 0): {
  172. "inspect.getmoduleinfo",
  173. "logging.warn",
  174. "logging.Logger.warn",
  175. "logging.LoggerAdapter.warn",
  176. "nntplib._NNTPBase.xpath",
  177. "platform.popen",
  178. "sqlite3.OptimizedUnicode",
  179. "time.clock",
  180. },
  181. (3, 4, 0): {
  182. "importlib.find_loader",
  183. "importlib.abc.Loader.load_module",
  184. "importlib.abc.Loader.module_repr",
  185. "importlib.abc.PathEntryFinder.find_loader",
  186. "importlib.abc.PathEntryFinder.find_module",
  187. "plistlib.readPlist",
  188. "plistlib.writePlist",
  189. "plistlib.readPlistFromBytes",
  190. "plistlib.writePlistToBytes",
  191. },
  192. (3, 4, 4): {"asyncio.tasks.async"},
  193. (3, 5, 0): {
  194. "fractions.gcd",
  195. "inspect.formatargspec",
  196. "inspect.getcallargs",
  197. "platform.linux_distribution",
  198. "platform.dist",
  199. },
  200. (3, 6, 0): {
  201. "importlib._bootstrap_external.FileLoader.load_module",
  202. "_ssl.RAND_pseudo_bytes",
  203. },
  204. (3, 7, 0): {
  205. "sys.set_coroutine_wrapper",
  206. "sys.get_coroutine_wrapper",
  207. "aifc.openfp",
  208. "threading.Thread.isAlive",
  209. "asyncio.Task.current_task",
  210. "asyncio.Task.all_task",
  211. "locale.format",
  212. "ssl.wrap_socket",
  213. "ssl.match_hostname",
  214. "sunau.openfp",
  215. "wave.openfp",
  216. },
  217. (3, 8, 0): {
  218. "gettext.lgettext",
  219. "gettext.ldgettext",
  220. "gettext.lngettext",
  221. "gettext.ldngettext",
  222. "gettext.bind_textdomain_codeset",
  223. "gettext.NullTranslations.output_charset",
  224. "gettext.NullTranslations.set_output_charset",
  225. "threading.Thread.isAlive",
  226. },
  227. (3, 9, 0): {
  228. "binascii.b2a_hqx",
  229. "binascii.a2b_hqx",
  230. "binascii.rlecode_hqx",
  231. "binascii.rledecode_hqx",
  232. },
  233. (3, 10, 0): {
  234. "_sqlite3.enable_shared_cache",
  235. "importlib.abc.Finder.find_module",
  236. "pathlib.Path.link_to",
  237. "zipimport.zipimporter.load_module",
  238. "zipimport.zipimporter.find_module",
  239. "zipimport.zipimporter.find_loader",
  240. "threading.currentThread",
  241. "threading.activeCount",
  242. "threading.Condition.notifyAll",
  243. "threading.Event.isSet",
  244. "threading.Thread.setName",
  245. "threading.Thread.getName",
  246. "threading.Thread.isDaemon",
  247. "threading.Thread.setDaemon",
  248. "cgi.log",
  249. },
  250. (3, 11, 0): {
  251. "importlib.resources.contents",
  252. "locale.getdefaultlocale",
  253. "locale.resetlocale",
  254. "re.template",
  255. "unittest.findTestCases",
  256. "unittest.makeSuite",
  257. "unittest.getTestCaseNames",
  258. "unittest.TestLoader.loadTestsFromModule",
  259. "unittest.TestLoader.loadTestsFromTestCase",
  260. "unittest.TestLoader.getTestCaseNames",
  261. "unittest.TestProgram.usageExit",
  262. },
  263. (3, 12, 0): {
  264. "asyncio.get_child_watcher",
  265. "asyncio.set_child_watcher",
  266. "asyncio.AbstractEventLoopPolicy.get_child_watcher",
  267. "asyncio.AbstractEventLoopPolicy.set_child_watcher",
  268. "builtins.bool.__invert__",
  269. "datetime.datetime.utcfromtimestamp",
  270. "datetime.datetime.utcnow",
  271. "pkgutil.find_loader",
  272. "pkgutil.get_loader",
  273. "pty.master_open",
  274. "pty.slave_open",
  275. "xml.etree.ElementTree.Element.__bool__",
  276. },
  277. (3, 13, 0): {
  278. "ctypes.SetPointerType",
  279. "pathlib.PurePath.is_reserved",
  280. "platform.java_ver",
  281. "pydoc.is_package",
  282. "sys._enablelegacywindowsfsencoding",
  283. "wave.Wave_read.getmark",
  284. "wave.Wave_read.getmarkers",
  285. "wave.Wave_read.setmark",
  286. "wave.Wave_write.getmark",
  287. "wave.Wave_write.getmarkers",
  288. "wave.Wave_write.setmark",
  289. },
  290. (3, 14, 0): {
  291. "asyncio.iscoroutinefunction",
  292. "asyncio.get_event_loop_policy",
  293. "asyncio.set_event_loop_policy",
  294. "codecs.open",
  295. "symtable.Class.get_methods",
  296. "sys._clear_type_cache",
  297. "sysconfig.expand_makefile_vars",
  298. },
  299. },
  300. }
  301. DEPRECATED_CLASSES: dict[tuple[int, int, int], dict[str, set[str]]] = {
  302. (3, 2, 0): {
  303. "configparser": {
  304. "LegacyInterpolation",
  305. "SafeConfigParser",
  306. },
  307. },
  308. (3, 3, 0): {
  309. "importlib.abc": {
  310. "Finder",
  311. },
  312. "pkgutil": {
  313. "ImpImporter",
  314. "ImpLoader",
  315. },
  316. "collections": {
  317. "Awaitable",
  318. "Coroutine",
  319. "AsyncIterable",
  320. "AsyncIterator",
  321. "AsyncGenerator",
  322. "Hashable",
  323. "Iterable",
  324. "Iterator",
  325. "Generator",
  326. "Reversible",
  327. "Sized",
  328. "Container",
  329. "Callable",
  330. "Collection",
  331. "Set",
  332. "MutableSet",
  333. "Mapping",
  334. "MutableMapping",
  335. "MappingView",
  336. "KeysView",
  337. "ItemsView",
  338. "ValuesView",
  339. "Sequence",
  340. "MutableSequence",
  341. "ByteString",
  342. },
  343. },
  344. (3, 9, 0): {
  345. "smtpd": {
  346. "MailmanProxy",
  347. }
  348. },
  349. (3, 11, 0): {
  350. "typing": {
  351. "Text",
  352. },
  353. "urllib.parse": {
  354. "Quoter",
  355. },
  356. "webbrowser": {
  357. "MacOSX",
  358. },
  359. },
  360. (3, 12, 0): {
  361. "ast": {
  362. "Bytes",
  363. "Ellipsis",
  364. "NameConstant",
  365. "Num",
  366. "Str",
  367. },
  368. "asyncio": {
  369. "AbstractChildWatcher",
  370. "MultiLoopChildWatcher",
  371. "FastChildWatcher",
  372. "SafeChildWatcher",
  373. },
  374. "collections.abc": {
  375. "ByteString",
  376. },
  377. "importlib.abc": {
  378. "ResourceReader",
  379. "Traversable",
  380. "TraversableResources",
  381. },
  382. "typing": {
  383. "ByteString",
  384. "Hashable",
  385. "Sized",
  386. },
  387. },
  388. (3, 13, 0): {
  389. "glob": {
  390. "glob.glob0",
  391. "glob.glob1",
  392. },
  393. "http.server": {
  394. "CGIHTTPRequestHandler",
  395. },
  396. },
  397. (3, 14, 0): {
  398. "argparse": {
  399. "FileType",
  400. },
  401. "asyncio": {
  402. "AbstractEventLoopPolicy",
  403. "DefaultEventLoopPolicy",
  404. "WindowsSelectorEventLoopPolicy",
  405. "WindowsProactorEventLoopPolicy",
  406. },
  407. "shutil": {
  408. "ExecError",
  409. },
  410. "typing": {
  411. "._UnionGenericAlias",
  412. },
  413. },
  414. }
  415. DEPRECATED_ATTRIBUTES: DeprecationDict = {
  416. (3, 2, 0): {
  417. "configparser.ParsingError.filename",
  418. },
  419. (3, 12, 0): {
  420. "calendar.January",
  421. "calendar.February",
  422. "sqlite3.version",
  423. "sqlite3.version_info",
  424. "sys.last_traceback",
  425. "sys.last_type",
  426. "sys.last_value",
  427. },
  428. (3, 13, 0): {
  429. "dis.HAVE_ARGUMENT",
  430. "tarfile.TarFile.tarfile",
  431. "traceback.TracebackException.exc_type",
  432. "typing.AnyStr",
  433. },
  434. }
  435. def _check_mode_str(mode: Any) -> bool:
  436. # check type
  437. if not isinstance(mode, str):
  438. return False
  439. # check syntax
  440. modes = set(mode)
  441. _mode = "rwatb+Ux"
  442. creating = "x" in modes
  443. if modes - set(_mode) or len(mode) > len(modes):
  444. return False
  445. # check logic
  446. reading = "r" in modes
  447. writing = "w" in modes
  448. appending = "a" in modes
  449. text = "t" in modes
  450. binary = "b" in modes
  451. if "U" in modes:
  452. if writing or appending or creating:
  453. return False
  454. reading = True
  455. if text and binary:
  456. return False
  457. total = reading + writing + appending + creating
  458. if total > 1:
  459. return False
  460. if not (reading or writing or appending or creating):
  461. return False
  462. return True
  463. class StdlibChecker(DeprecatedMixin, BaseChecker):
  464. name = "stdlib"
  465. msgs: dict[str, MessageDefinitionTuple] = {
  466. **DeprecatedMixin.DEPRECATED_METHOD_MESSAGE,
  467. **DeprecatedMixin.DEPRECATED_ARGUMENT_MESSAGE,
  468. **DeprecatedMixin.DEPRECATED_CLASS_MESSAGE,
  469. **DeprecatedMixin.DEPRECATED_DECORATOR_MESSAGE,
  470. **DeprecatedMixin.DEPRECATED_ATTRIBUTE_MESSAGE,
  471. "W1501": (
  472. '"%s" is not a valid mode for open.',
  473. "bad-open-mode",
  474. "Python supports: r, w, a[, x] modes with b, +, "
  475. "and U (only with r) options. "
  476. "See https://docs.python.org/3/library/functions.html#open",
  477. ),
  478. "W1502": (
  479. "Using datetime.time in a boolean context.",
  480. "boolean-datetime",
  481. "Using datetime.time in a boolean context can hide "
  482. "subtle bugs when the time they represent matches "
  483. "midnight UTC. This behaviour was fixed in Python 3.5. "
  484. "See https://bugs.python.org/issue13936 for reference.",
  485. {"maxversion": (3, 5)},
  486. ),
  487. "W1503": (
  488. "Redundant use of %s with constant value %r",
  489. "redundant-unittest-assert",
  490. "The first argument of assertTrue and assertFalse is "
  491. "a condition. If a constant is passed as parameter, that "
  492. "condition will be always true. In this case a warning "
  493. "should be emitted.",
  494. ),
  495. "W1506": (
  496. "threading.Thread needs the target function",
  497. "bad-thread-instantiation",
  498. "The warning is emitted when a threading.Thread class "
  499. "is instantiated without the target function being passed as a kwarg or as a second argument. "
  500. "By default, the first parameter is the group param, not the target param.",
  501. ),
  502. "W1507": (
  503. "Using copy.copy(os.environ). Use os.environ.copy() instead.",
  504. "shallow-copy-environ",
  505. "os.environ is not a dict object but proxy object, so "
  506. "shallow copy has still effects on original object. "
  507. "See https://bugs.python.org/issue15373 for reference.",
  508. ),
  509. "E1507": (
  510. "%s does not support %s type argument",
  511. "invalid-envvar-value",
  512. "Env manipulation functions support only string type arguments. "
  513. "See https://docs.python.org/3/library/os.html#os.getenv.",
  514. ),
  515. "E1519": (
  516. "singledispatch decorator should not be used with methods, "
  517. "use singledispatchmethod instead.",
  518. "singledispatch-method",
  519. "singledispatch should decorate functions and not class/instance methods. "
  520. "Use singledispatchmethod for those cases.",
  521. ),
  522. "E1520": (
  523. "singledispatchmethod decorator should not be used with functions, "
  524. "use singledispatch instead.",
  525. "singledispatchmethod-function",
  526. "singledispatchmethod should decorate class/instance methods and not functions. "
  527. "Use singledispatch for those cases.",
  528. ),
  529. "W1508": (
  530. "%s default type is %s. Expected str or None.",
  531. "invalid-envvar-default",
  532. "Env manipulation functions return None or str values. "
  533. "Supplying anything different as a default may cause bugs. "
  534. "See https://docs.python.org/3/library/os.html#os.getenv.",
  535. ),
  536. "W1509": (
  537. "Using preexec_fn keyword which may be unsafe in the presence "
  538. "of threads",
  539. "subprocess-popen-preexec-fn",
  540. "The preexec_fn parameter is not safe to use in the presence "
  541. "of threads in your application. The child process could "
  542. "deadlock before exec is called. If you must use it, keep it "
  543. "trivial! Minimize the number of libraries you call into. "
  544. "See https://docs.python.org/3/library/subprocess.html#popen-constructor",
  545. ),
  546. "W1510": (
  547. "'subprocess.run' used without explicitly defining the value for 'check'.",
  548. "subprocess-run-check",
  549. "The ``check`` keyword is set to False by default. It means the process "
  550. "launched by ``subprocess.run`` can exit with a non-zero exit code and "
  551. "fail silently. It's better to set it explicitly to make clear what the "
  552. "error-handling behavior is.",
  553. ),
  554. "W1514": (
  555. "Using open without explicitly specifying an encoding",
  556. "unspecified-encoding",
  557. "It is better to specify an encoding when opening documents. "
  558. "Using the system default implicitly can create problems on other operating systems. "
  559. "See https://peps.python.org/pep-0597/",
  560. {"maxversion": (3, 15)},
  561. ),
  562. "W1515": (
  563. "Leaving functions creating breakpoints in production code is not recommended",
  564. "forgotten-debug-statement",
  565. "Calls to breakpoint(), sys.breakpointhook() and pdb.set_trace() should be removed "
  566. "from code that is not actively being debugged.",
  567. ),
  568. "W1518": (
  569. "'lru_cache(maxsize=None)' or 'cache' will keep all method args alive indefinitely, including 'self'",
  570. "method-cache-max-size-none",
  571. "By decorating a method with lru_cache or cache the 'self' argument will be linked to "
  572. "the function and therefore never garbage collected. Unless your instance "
  573. "will never need to be garbage collected (singleton) it is recommended to refactor "
  574. "code to avoid this pattern or add a maxsize to the cache. "
  575. "The default value for maxsize is 128.",
  576. {
  577. "old_names": [
  578. ("W1516", "lru-cache-decorating-method"),
  579. ("W1517", "cache-max-size-none"),
  580. ]
  581. },
  582. ),
  583. }
  584. def __init__(self, linter: PyLinter) -> None:
  585. BaseChecker.__init__(self, linter)
  586. self._deprecated_methods: set[str] = set()
  587. self._deprecated_arguments: dict[str, tuple[tuple[int | None, str], ...]] = {}
  588. self._deprecated_classes: dict[str, set[str]] = {}
  589. self._deprecated_decorators: set[str] = set()
  590. self._deprecated_attributes: set[str] = set()
  591. for since_vers, func_list in DEPRECATED_METHODS[sys.version_info[0]].items():
  592. if since_vers <= sys.version_info:
  593. self._deprecated_methods.update(func_list)
  594. for since_vers, args_list in DEPRECATED_ARGUMENTS.items():
  595. if since_vers <= sys.version_info:
  596. self._deprecated_arguments.update(args_list)
  597. for since_vers, class_list in DEPRECATED_CLASSES.items():
  598. if since_vers <= sys.version_info:
  599. self._deprecated_classes.update(class_list)
  600. for since_vers, decorator_list in DEPRECATED_DECORATORS.items():
  601. if since_vers <= sys.version_info:
  602. self._deprecated_decorators.update(decorator_list)
  603. for since_vers, attribute_list in DEPRECATED_ATTRIBUTES.items():
  604. if since_vers <= sys.version_info:
  605. self._deprecated_attributes.update(attribute_list)
  606. # Modules are checked by the ImportsChecker, because the list is
  607. # synced with the config argument deprecated-modules
  608. def _check_bad_thread_instantiation(self, node: nodes.Call) -> None:
  609. func_kwargs = {key.arg for key in node.keywords}
  610. if "target" in func_kwargs:
  611. return
  612. if len(node.args) < 2 and not (node.kwargs and "target" in func_kwargs):
  613. self.add_message(
  614. "bad-thread-instantiation", node=node, confidence=interfaces.HIGH
  615. )
  616. def _check_for_preexec_fn_in_popen(self, node: nodes.Call) -> None:
  617. if node.keywords:
  618. for keyword in node.keywords:
  619. if keyword.arg == "preexec_fn":
  620. self.add_message("subprocess-popen-preexec-fn", node=node)
  621. def _check_for_check_kw_in_run(self, node: nodes.Call) -> None:
  622. kwargs = {keyword.arg for keyword in (node.keywords or ())}
  623. if "check" not in kwargs:
  624. self.add_message("subprocess-run-check", node=node, confidence=INFERENCE)
  625. def _check_shallow_copy_environ(self, node: nodes.Call) -> None:
  626. confidence = HIGH
  627. try:
  628. arg = utils.get_argument_from_call(node, position=0, keyword="x")
  629. except utils.NoSuchArgumentError:
  630. arg = utils.infer_kwarg_from_call(node, keyword="x")
  631. if not arg:
  632. return
  633. confidence = INFERENCE
  634. try:
  635. inferred_args = arg.inferred()
  636. except astroid.InferenceError:
  637. return
  638. for inferred in inferred_args:
  639. if inferred.qname() == OS_ENVIRON:
  640. self.add_message(
  641. "shallow-copy-environ", node=node, confidence=confidence
  642. )
  643. break
  644. @utils.only_required_for_messages(
  645. "bad-open-mode",
  646. "redundant-unittest-assert",
  647. "deprecated-method",
  648. "deprecated-argument",
  649. "bad-thread-instantiation",
  650. "shallow-copy-environ",
  651. "invalid-envvar-value",
  652. "invalid-envvar-default",
  653. "subprocess-popen-preexec-fn",
  654. "subprocess-run-check",
  655. "deprecated-class",
  656. "unspecified-encoding",
  657. "forgotten-debug-statement",
  658. )
  659. def visit_call(self, node: nodes.Call) -> None:
  660. """Visit a Call node."""
  661. self.check_deprecated_class_in_call(node)
  662. for inferred in utils.infer_all(node.func):
  663. if isinstance(inferred, util.UninferableBase):
  664. continue
  665. if inferred.root().name in OPEN_MODULE:
  666. open_func_name: str | None = None
  667. if isinstance(node.func, nodes.Name):
  668. open_func_name = node.func.name
  669. if isinstance(node.func, nodes.Attribute):
  670. open_func_name = node.func.attrname
  671. if open_func_name in OPEN_FILES_FUNCS:
  672. self._check_open_call(node, inferred.root().name, open_func_name)
  673. elif inferred.root().name == UNITTEST_CASE:
  674. self._check_redundant_assert(node, inferred)
  675. elif isinstance(inferred, nodes.ClassDef):
  676. if inferred.qname() == THREADING_THREAD:
  677. self._check_bad_thread_instantiation(node)
  678. elif inferred.qname() == SUBPROCESS_POPEN:
  679. self._check_for_preexec_fn_in_popen(node)
  680. elif isinstance(inferred, nodes.FunctionDef):
  681. name = inferred.qname()
  682. if name == COPY_COPY:
  683. self._check_shallow_copy_environ(node)
  684. elif name in ENV_GETTERS:
  685. self._check_env_function(node, inferred)
  686. elif name == SUBPROCESS_RUN:
  687. self._check_for_check_kw_in_run(node)
  688. elif name in DEBUG_BREAKPOINTS:
  689. self.add_message("forgotten-debug-statement", node=node)
  690. self.check_deprecated_method(node, inferred)
  691. @utils.only_required_for_messages("boolean-datetime")
  692. def visit_unaryop(self, node: nodes.UnaryOp) -> None:
  693. if node.op == "not":
  694. self._check_datetime(node.operand)
  695. @utils.only_required_for_messages("boolean-datetime")
  696. def visit_if(self, node: nodes.If) -> None:
  697. self._check_datetime(node.test)
  698. @utils.only_required_for_messages("boolean-datetime")
  699. def visit_ifexp(self, node: nodes.IfExp) -> None:
  700. self._check_datetime(node.test)
  701. @utils.only_required_for_messages("boolean-datetime")
  702. def visit_boolop(self, node: nodes.BoolOp) -> None:
  703. for value in node.values:
  704. self._check_datetime(value)
  705. @utils.only_required_for_messages(
  706. "method-cache-max-size-none",
  707. "singledispatch-method",
  708. "singledispatchmethod-function",
  709. )
  710. def visit_functiondef(self, node: nodes.FunctionDef) -> None:
  711. if node.decorators:
  712. if isinstance(node.parent, nodes.ClassDef):
  713. self._check_lru_cache_decorators(node)
  714. self._check_dispatch_decorators(node)
  715. def _check_lru_cache_decorators(self, node: nodes.FunctionDef) -> None:
  716. """Check if instance methods are decorated with functools.lru_cache."""
  717. if any(utils.is_enum(ancestor) for ancestor in node.parent.ancestors()):
  718. # method of class inheriting from Enum is exempt from this check.
  719. return
  720. lru_cache_nodes: list[nodes.NodeNG] = []
  721. for d_node in node.decorators.nodes:
  722. # pylint: disable = too-many-try-statements
  723. try:
  724. for infered_node in d_node.infer():
  725. q_name = infered_node.qname()
  726. if q_name in NON_INSTANCE_METHODS:
  727. return
  728. # Check if there is a maxsize argument set to None in the call
  729. if q_name in LRU_CACHE and isinstance(d_node, nodes.Call):
  730. try:
  731. arg = utils.get_argument_from_call(
  732. d_node, position=0, keyword="maxsize"
  733. )
  734. except utils.NoSuchArgumentError:
  735. arg = utils.infer_kwarg_from_call(d_node, "maxsize")
  736. if not isinstance(arg, nodes.Const) or arg.value is not None:
  737. break
  738. lru_cache_nodes.append(d_node)
  739. break
  740. if q_name == "functools.cache":
  741. lru_cache_nodes.append(d_node)
  742. break
  743. except astroid.InferenceError:
  744. pass
  745. for lru_cache_node in lru_cache_nodes:
  746. self.add_message(
  747. "method-cache-max-size-none",
  748. node=lru_cache_node,
  749. confidence=interfaces.INFERENCE,
  750. )
  751. def _check_dispatch_decorators(self, node: nodes.FunctionDef) -> None:
  752. decorators_map: dict[str, tuple[nodes.NodeNG, interfaces.Confidence]] = {}
  753. for decorator in node.decorators.nodes:
  754. if isinstance(decorator, nodes.Name) and decorator.name:
  755. decorators_map[decorator.name] = (decorator, interfaces.HIGH)
  756. elif utils.is_registered_in_singledispatch_function(node):
  757. decorators_map["singledispatch"] = (decorator, interfaces.INFERENCE)
  758. elif utils.is_registered_in_singledispatchmethod_function(node):
  759. decorators_map["singledispatchmethod"] = (
  760. decorator,
  761. interfaces.INFERENCE,
  762. )
  763. if node.is_method():
  764. if "singledispatch" in decorators_map:
  765. self.add_message(
  766. "singledispatch-method",
  767. node=decorators_map["singledispatch"][0],
  768. confidence=decorators_map["singledispatch"][1],
  769. )
  770. elif "singledispatchmethod" in decorators_map:
  771. self.add_message(
  772. "singledispatchmethod-function",
  773. node=decorators_map["singledispatchmethod"][0],
  774. confidence=decorators_map["singledispatchmethod"][1],
  775. )
  776. def _check_redundant_assert(self, node: nodes.Call, infer: InferenceResult) -> None:
  777. if (
  778. isinstance(infer, astroid.BoundMethod)
  779. and node.args
  780. and isinstance(node.args[0], nodes.Const)
  781. and infer.name in {"assertTrue", "assertFalse"}
  782. ):
  783. self.add_message(
  784. "redundant-unittest-assert",
  785. args=(infer.name, node.args[0].value),
  786. node=node,
  787. )
  788. def _check_datetime(self, node: nodes.NodeNG) -> None:
  789. """Check that a datetime was inferred, if so, emit boolean-datetime warning."""
  790. try:
  791. inferred = next(node.infer())
  792. except astroid.InferenceError:
  793. return
  794. if isinstance(inferred, astroid.Instance) and inferred.qname() in {
  795. "_pydatetime.time",
  796. "datetime.time",
  797. }:
  798. self.add_message("boolean-datetime", node=node)
  799. def _check_open_call(
  800. self, node: nodes.Call, open_module: str, func_name: str
  801. ) -> None:
  802. """Various checks for an open call."""
  803. mode_arg = None
  804. confidence = HIGH
  805. try:
  806. if open_module == "_io":
  807. mode_arg = utils.get_argument_from_call(
  808. node, position=1, keyword="mode"
  809. )
  810. elif open_module in PATHLIB_MODULE:
  811. mode_arg = utils.get_argument_from_call(
  812. node, position=0, keyword="mode"
  813. )
  814. except utils.NoSuchArgumentError:
  815. mode_arg = utils.infer_kwarg_from_call(node, keyword="mode")
  816. if mode_arg:
  817. confidence = INFERENCE
  818. if mode_arg:
  819. mode_arg = utils.safe_infer(mode_arg)
  820. if (
  821. func_name in OPEN_FILES_MODE
  822. and isinstance(mode_arg, nodes.Const)
  823. and not _check_mode_str(mode_arg.value)
  824. ):
  825. self.add_message(
  826. "bad-open-mode",
  827. node=node,
  828. args=mode_arg.value or str(mode_arg.value),
  829. confidence=confidence,
  830. )
  831. if not mode_arg or (
  832. isinstance(mode_arg, nodes.Const)
  833. and not (mode_arg.value and "b" in str(mode_arg.value))
  834. ):
  835. confidence = HIGH
  836. try:
  837. if open_module in PATHLIB_MODULE:
  838. match node.func.attrname:
  839. case "read_text":
  840. encoding_arg = utils.get_argument_from_call(
  841. node, position=0, keyword="encoding"
  842. )
  843. case "write_text":
  844. encoding_arg = utils.get_argument_from_call(
  845. node, position=1, keyword="encoding"
  846. )
  847. case _:
  848. encoding_arg = utils.get_argument_from_call(
  849. node, position=2, keyword="encoding"
  850. )
  851. else:
  852. encoding_arg = utils.get_argument_from_call(
  853. node, position=3, keyword="encoding"
  854. )
  855. except utils.NoSuchArgumentError:
  856. encoding_arg = utils.infer_kwarg_from_call(node, keyword="encoding")
  857. if encoding_arg:
  858. confidence = INFERENCE
  859. else:
  860. self.add_message(
  861. "unspecified-encoding", node=node, confidence=confidence
  862. )
  863. if encoding_arg:
  864. encoding_arg = utils.safe_infer(encoding_arg)
  865. if isinstance(encoding_arg, nodes.Const) and encoding_arg.value is None:
  866. self.add_message(
  867. "unspecified-encoding", node=node, confidence=confidence
  868. )
  869. def _check_env_function(self, node: nodes.Call, infer: nodes.FunctionDef) -> None:
  870. env_name_kwarg = "key"
  871. env_value_kwarg = "default"
  872. if node.keywords:
  873. kwargs = {keyword.arg: keyword.value for keyword in node.keywords}
  874. else:
  875. kwargs = None
  876. if node.args:
  877. env_name_arg = node.args[0]
  878. elif kwargs and env_name_kwarg in kwargs:
  879. env_name_arg = kwargs[env_name_kwarg]
  880. else:
  881. env_name_arg = None
  882. if env_name_arg:
  883. self._check_invalid_envvar_value(
  884. node=node,
  885. message="invalid-envvar-value",
  886. call_arg=utils.safe_infer(env_name_arg),
  887. infer=infer,
  888. allow_none=False,
  889. )
  890. if len(node.args) == 2:
  891. env_value_arg = node.args[1]
  892. elif kwargs and env_value_kwarg in kwargs:
  893. env_value_arg = kwargs[env_value_kwarg]
  894. else:
  895. env_value_arg = None
  896. if env_value_arg:
  897. self._check_invalid_envvar_value(
  898. node=node,
  899. infer=infer,
  900. message="invalid-envvar-default",
  901. call_arg=utils.safe_infer(env_value_arg),
  902. allow_none=True,
  903. )
  904. def _check_invalid_envvar_value(
  905. self,
  906. node: nodes.Call,
  907. infer: nodes.FunctionDef,
  908. message: str,
  909. call_arg: InferenceResult | None,
  910. allow_none: bool,
  911. ) -> None:
  912. if call_arg is None or isinstance(call_arg, util.UninferableBase):
  913. return
  914. name = infer.qname()
  915. if isinstance(call_arg, nodes.Const):
  916. emit = False
  917. match call_arg.value:
  918. case None:
  919. emit = not allow_none
  920. case str():
  921. pass
  922. case _:
  923. emit = True
  924. if emit:
  925. self.add_message(message, node=node, args=(name, call_arg.pytype()))
  926. else:
  927. self.add_message(message, node=node, args=(name, call_arg.pytype()))
  928. def deprecated_methods(self) -> set[str]:
  929. return self._deprecated_methods
  930. def deprecated_arguments(self, method: str) -> tuple[tuple[int | None, str], ...]:
  931. return self._deprecated_arguments.get(method, ())
  932. def deprecated_classes(self, module: str) -> Iterable[str]:
  933. return self._deprecated_classes.get(module, ())
  934. def deprecated_decorators(self) -> Iterable[str]:
  935. return self._deprecated_decorators
  936. def deprecated_attributes(self) -> Iterable[str]:
  937. return self._deprecated_attributes
  938. def register(linter: PyLinter) -> None:
  939. linter.register_checker(StdlibChecker(linter))