Marco Ricci commited on 2024-07-20 23:46:05
Zeige 14 geänderte Dateien mit 1049 Einfügungen und 247 Löschungen.
Because `ruff` only shows online documentation for the latest version, it is inconvenient to work with outdated rulesets. So, manually update the `hatch` configuration from `ruff` v0.4.5 to v0.5.0, and reimplement all rule changes by hand.
| ... | ... |
@@ -118,9 +118,34 @@ exclude_also = [ |
| 118 | 118 |
[tool.ruff] |
| 119 | 119 |
line-length = 79 |
| 120 | 120 |
src = ["src"] |
| 121 |
+extend = "ruff_defaults_v0.5.0.toml" |
|
| 121 | 122 |
|
| 122 | 123 |
[tool.ruff.format] |
| 123 | 124 |
quote-style = 'single' |
| 125 |
+preview = true |
|
| 126 |
+ |
|
| 127 |
+[tool.ruff.lint] |
|
| 128 |
+preview = true |
|
| 129 |
+extend-ignore = [ |
|
| 130 |
+ 'S101', |
|
| 131 |
+] |
|
| 132 |
+extend-select = [ |
|
| 133 |
+ 'E501', |
|
| 134 |
+] |
|
| 124 | 135 |
|
| 125 | 136 |
[tool.ruff.lint.pydocstyle] |
| 126 | 137 |
convention = 'google' |
| 138 |
+ |
|
| 139 |
+[tool.ruff.lint.flake8-pytest-style] |
|
| 140 |
+parametrize-names-type = 'list' |
|
| 141 |
+ |
|
| 142 |
+[tool.ruff.lint.extend-per-file-ignores] |
|
| 143 |
+"**/tests/**/*" = [ |
|
| 144 |
+ 'SLF001', |
|
| 145 |
+ 'A002', |
|
| 146 |
+ 'FBT001', |
|
| 147 |
+] |
|
| 148 |
+ |
|
| 149 |
+[tool.hatch.envs.hatch-static-analysis] |
|
| 150 |
+config-path = "ruff_defaults_v0.5.0.toml" |
|
| 151 |
+dependencies = ["ruff==0.5.0"] |
| ... | ... |
@@ -0,0 +1,686 @@ |
| 1 |
+line-length = 120 |
|
| 2 |
+ |
|
| 3 |
+[format] |
|
| 4 |
+docstring-code-format = true |
|
| 5 |
+docstring-code-line-length = 80 |
|
| 6 |
+ |
|
| 7 |
+[lint] |
|
| 8 |
+select = [ |
|
| 9 |
+ "A001", |
|
| 10 |
+ "A002", |
|
| 11 |
+ "A003", |
|
| 12 |
+ "ARG001", |
|
| 13 |
+ "ARG002", |
|
| 14 |
+ "ARG003", |
|
| 15 |
+ "ARG004", |
|
| 16 |
+ "ARG005", |
|
| 17 |
+ "ASYNC100", |
|
| 18 |
+ "ASYNC105", |
|
| 19 |
+ "ASYNC109", |
|
| 20 |
+ "ASYNC110", |
|
| 21 |
+ "ASYNC115", |
|
| 22 |
+ "ASYNC210", |
|
| 23 |
+ "ASYNC220", |
|
| 24 |
+ "ASYNC221", |
|
| 25 |
+ "ASYNC230", |
|
| 26 |
+ "ASYNC251", |
|
| 27 |
+ "B002", |
|
| 28 |
+ "B003", |
|
| 29 |
+ "B004", |
|
| 30 |
+ "B005", |
|
| 31 |
+ "B006", |
|
| 32 |
+ "B007", |
|
| 33 |
+ "B008", |
|
| 34 |
+ "B009", |
|
| 35 |
+ "B010", |
|
| 36 |
+ "B011", |
|
| 37 |
+ "B012", |
|
| 38 |
+ "B013", |
|
| 39 |
+ "B014", |
|
| 40 |
+ "B015", |
|
| 41 |
+ "B016", |
|
| 42 |
+ "B017", |
|
| 43 |
+ "B018", |
|
| 44 |
+ "B019", |
|
| 45 |
+ "B020", |
|
| 46 |
+ "B021", |
|
| 47 |
+ "B022", |
|
| 48 |
+ "B023", |
|
| 49 |
+ "B024", |
|
| 50 |
+ "B025", |
|
| 51 |
+ "B026", |
|
| 52 |
+ "B028", |
|
| 53 |
+ "B029", |
|
| 54 |
+ "B030", |
|
| 55 |
+ "B031", |
|
| 56 |
+ "B032", |
|
| 57 |
+ "B033", |
|
| 58 |
+ "B034", |
|
| 59 |
+ "B035", |
|
| 60 |
+ "B904", |
|
| 61 |
+ "B905", |
|
| 62 |
+ "B909", |
|
| 63 |
+ "BLE001", |
|
| 64 |
+ "C400", |
|
| 65 |
+ "C401", |
|
| 66 |
+ "C402", |
|
| 67 |
+ "C403", |
|
| 68 |
+ "C404", |
|
| 69 |
+ "C405", |
|
| 70 |
+ "C406", |
|
| 71 |
+ "C408", |
|
| 72 |
+ "C409", |
|
| 73 |
+ "C410", |
|
| 74 |
+ "C411", |
|
| 75 |
+ "C413", |
|
| 76 |
+ "C414", |
|
| 77 |
+ "C415", |
|
| 78 |
+ "C416", |
|
| 79 |
+ "C417", |
|
| 80 |
+ "C418", |
|
| 81 |
+ "C419", |
|
| 82 |
+ "COM818", |
|
| 83 |
+ "DTZ001", |
|
| 84 |
+ "DTZ002", |
|
| 85 |
+ "DTZ003", |
|
| 86 |
+ "DTZ004", |
|
| 87 |
+ "DTZ005", |
|
| 88 |
+ "DTZ006", |
|
| 89 |
+ "DTZ007", |
|
| 90 |
+ "DTZ011", |
|
| 91 |
+ "DTZ012", |
|
| 92 |
+ "E101", |
|
| 93 |
+ "E112", |
|
| 94 |
+ "E113", |
|
| 95 |
+ "E115", |
|
| 96 |
+ "E116", |
|
| 97 |
+ "E201", |
|
| 98 |
+ "E202", |
|
| 99 |
+ "E203", |
|
| 100 |
+ "E211", |
|
| 101 |
+ "E221", |
|
| 102 |
+ "E222", |
|
| 103 |
+ "E223", |
|
| 104 |
+ "E224", |
|
| 105 |
+ "E225", |
|
| 106 |
+ "E226", |
|
| 107 |
+ "E227", |
|
| 108 |
+ "E228", |
|
| 109 |
+ "E231", |
|
| 110 |
+ "E241", |
|
| 111 |
+ "E242", |
|
| 112 |
+ "E251", |
|
| 113 |
+ "E252", |
|
| 114 |
+ "E261", |
|
| 115 |
+ "E262", |
|
| 116 |
+ "E265", |
|
| 117 |
+ "E266", |
|
| 118 |
+ "E271", |
|
| 119 |
+ "E272", |
|
| 120 |
+ "E273", |
|
| 121 |
+ "E274", |
|
| 122 |
+ "E275", |
|
| 123 |
+ "E401", |
|
| 124 |
+ "E402", |
|
| 125 |
+ "E502", |
|
| 126 |
+ "E701", |
|
| 127 |
+ "E702", |
|
| 128 |
+ "E703", |
|
| 129 |
+ "E711", |
|
| 130 |
+ "E712", |
|
| 131 |
+ "E713", |
|
| 132 |
+ "E714", |
|
| 133 |
+ "E721", |
|
| 134 |
+ "E722", |
|
| 135 |
+ "E731", |
|
| 136 |
+ "E741", |
|
| 137 |
+ "E742", |
|
| 138 |
+ "E743", |
|
| 139 |
+ "E902", |
|
| 140 |
+ "EM101", |
|
| 141 |
+ "EM102", |
|
| 142 |
+ "EM103", |
|
| 143 |
+ "EXE001", |
|
| 144 |
+ "EXE002", |
|
| 145 |
+ "EXE003", |
|
| 146 |
+ "EXE004", |
|
| 147 |
+ "EXE005", |
|
| 148 |
+ "F401", |
|
| 149 |
+ "F402", |
|
| 150 |
+ "F403", |
|
| 151 |
+ "F404", |
|
| 152 |
+ "F405", |
|
| 153 |
+ "F406", |
|
| 154 |
+ "F407", |
|
| 155 |
+ "F501", |
|
| 156 |
+ "F502", |
|
| 157 |
+ "F503", |
|
| 158 |
+ "F504", |
|
| 159 |
+ "F505", |
|
| 160 |
+ "F506", |
|
| 161 |
+ "F507", |
|
| 162 |
+ "F508", |
|
| 163 |
+ "F509", |
|
| 164 |
+ "F521", |
|
| 165 |
+ "F522", |
|
| 166 |
+ "F523", |
|
| 167 |
+ "F524", |
|
| 168 |
+ "F525", |
|
| 169 |
+ "F541", |
|
| 170 |
+ "F601", |
|
| 171 |
+ "F602", |
|
| 172 |
+ "F621", |
|
| 173 |
+ "F622", |
|
| 174 |
+ "F631", |
|
| 175 |
+ "F632", |
|
| 176 |
+ "F633", |
|
| 177 |
+ "F634", |
|
| 178 |
+ "F701", |
|
| 179 |
+ "F702", |
|
| 180 |
+ "F704", |
|
| 181 |
+ "F706", |
|
| 182 |
+ "F707", |
|
| 183 |
+ "F722", |
|
| 184 |
+ "F811", |
|
| 185 |
+ "F821", |
|
| 186 |
+ "F822", |
|
| 187 |
+ "F823", |
|
| 188 |
+ "F841", |
|
| 189 |
+ "F842", |
|
| 190 |
+ "F901", |
|
| 191 |
+ "FA100", |
|
| 192 |
+ "FA102", |
|
| 193 |
+ "FBT001", |
|
| 194 |
+ "FBT002", |
|
| 195 |
+ "FLY002", |
|
| 196 |
+ "FURB105", |
|
| 197 |
+ "FURB110", |
|
| 198 |
+ "FURB113", |
|
| 199 |
+ "FURB116", |
|
| 200 |
+ "FURB118", |
|
| 201 |
+ "FURB129", |
|
| 202 |
+ "FURB131", |
|
| 203 |
+ "FURB132", |
|
| 204 |
+ "FURB136", |
|
| 205 |
+ "FURB142", |
|
| 206 |
+ "FURB145", |
|
| 207 |
+ "FURB148", |
|
| 208 |
+ "FURB152", |
|
| 209 |
+ "FURB157", |
|
| 210 |
+ "FURB161", |
|
| 211 |
+ "FURB163", |
|
| 212 |
+ "FURB164", |
|
| 213 |
+ "FURB166", |
|
| 214 |
+ "FURB167", |
|
| 215 |
+ "FURB168", |
|
| 216 |
+ "FURB169", |
|
| 217 |
+ "FURB171", |
|
| 218 |
+ "FURB177", |
|
| 219 |
+ "FURB180", |
|
| 220 |
+ "FURB181", |
|
| 221 |
+ "FURB187", |
|
| 222 |
+ "FURB192", |
|
| 223 |
+ "G001", |
|
| 224 |
+ "G002", |
|
| 225 |
+ "G003", |
|
| 226 |
+ "G004", |
|
| 227 |
+ "G010", |
|
| 228 |
+ "G101", |
|
| 229 |
+ "G201", |
|
| 230 |
+ "G202", |
|
| 231 |
+ "I001", |
|
| 232 |
+ "I002", |
|
| 233 |
+ "ICN001", |
|
| 234 |
+ "ICN002", |
|
| 235 |
+ "ICN003", |
|
| 236 |
+ "INP001", |
|
| 237 |
+ "INT001", |
|
| 238 |
+ "INT002", |
|
| 239 |
+ "INT003", |
|
| 240 |
+ "ISC003", |
|
| 241 |
+ "LOG001", |
|
| 242 |
+ "LOG002", |
|
| 243 |
+ "LOG007", |
|
| 244 |
+ "LOG009", |
|
| 245 |
+ "N801", |
|
| 246 |
+ "N802", |
|
| 247 |
+ "N803", |
|
| 248 |
+ "N804", |
|
| 249 |
+ "N805", |
|
| 250 |
+ "N806", |
|
| 251 |
+ "N807", |
|
| 252 |
+ "N811", |
|
| 253 |
+ "N812", |
|
| 254 |
+ "N813", |
|
| 255 |
+ "N814", |
|
| 256 |
+ "N815", |
|
| 257 |
+ "N816", |
|
| 258 |
+ "N817", |
|
| 259 |
+ "N818", |
|
| 260 |
+ "N999", |
|
| 261 |
+ "PERF101", |
|
| 262 |
+ "PERF102", |
|
| 263 |
+ "PERF401", |
|
| 264 |
+ "PERF402", |
|
| 265 |
+ "PERF403", |
|
| 266 |
+ "PGH005", |
|
| 267 |
+ "PIE790", |
|
| 268 |
+ "PIE794", |
|
| 269 |
+ "PIE796", |
|
| 270 |
+ "PIE800", |
|
| 271 |
+ "PIE804", |
|
| 272 |
+ "PIE807", |
|
| 273 |
+ "PIE808", |
|
| 274 |
+ "PIE810", |
|
| 275 |
+ "PLC0105", |
|
| 276 |
+ "PLC0131", |
|
| 277 |
+ "PLC0132", |
|
| 278 |
+ "PLC0205", |
|
| 279 |
+ "PLC0208", |
|
| 280 |
+ "PLC0414", |
|
| 281 |
+ "PLC0415", |
|
| 282 |
+ "PLC1901", |
|
| 283 |
+ "PLC2401", |
|
| 284 |
+ "PLC2403", |
|
| 285 |
+ "PLC2701", |
|
| 286 |
+ "PLC2801", |
|
| 287 |
+ "PLC3002", |
|
| 288 |
+ "PLE0100", |
|
| 289 |
+ "PLE0101", |
|
| 290 |
+ "PLE0115", |
|
| 291 |
+ "PLE0116", |
|
| 292 |
+ "PLE0117", |
|
| 293 |
+ "PLE0118", |
|
| 294 |
+ "PLE0237", |
|
| 295 |
+ "PLE0241", |
|
| 296 |
+ "PLE0302", |
|
| 297 |
+ "PLE0303", |
|
| 298 |
+ "PLE0304", |
|
| 299 |
+ "PLE0305", |
|
| 300 |
+ "PLE0307", |
|
| 301 |
+ "PLE0308", |
|
| 302 |
+ "PLE0309", |
|
| 303 |
+ "PLE0604", |
|
| 304 |
+ "PLE0605", |
|
| 305 |
+ "PLE0643", |
|
| 306 |
+ "PLE0704", |
|
| 307 |
+ "PLE1132", |
|
| 308 |
+ "PLE1141", |
|
| 309 |
+ "PLE1142", |
|
| 310 |
+ "PLE1205", |
|
| 311 |
+ "PLE1206", |
|
| 312 |
+ "PLE1300", |
|
| 313 |
+ "PLE1307", |
|
| 314 |
+ "PLE1310", |
|
| 315 |
+ "PLE1507", |
|
| 316 |
+ "PLE1519", |
|
| 317 |
+ "PLE1520", |
|
| 318 |
+ "PLE1700", |
|
| 319 |
+ "PLE2502", |
|
| 320 |
+ "PLE2510", |
|
| 321 |
+ "PLE2512", |
|
| 322 |
+ "PLE2513", |
|
| 323 |
+ "PLE2514", |
|
| 324 |
+ "PLE2515", |
|
| 325 |
+ "PLE4703", |
|
| 326 |
+ "PLR0124", |
|
| 327 |
+ "PLR0133", |
|
| 328 |
+ "PLR0202", |
|
| 329 |
+ "PLR0203", |
|
| 330 |
+ "PLR0206", |
|
| 331 |
+ "PLR0402", |
|
| 332 |
+ "PLR1704", |
|
| 333 |
+ "PLR1711", |
|
| 334 |
+ "PLR1714", |
|
| 335 |
+ "PLR1722", |
|
| 336 |
+ "PLR1730", |
|
| 337 |
+ "PLR1733", |
|
| 338 |
+ "PLR1736", |
|
| 339 |
+ "PLR2004", |
|
| 340 |
+ "PLR2044", |
|
| 341 |
+ "PLR5501", |
|
| 342 |
+ "PLR6104", |
|
| 343 |
+ "PLR6201", |
|
| 344 |
+ "PLR6301", |
|
| 345 |
+ "PLW0108", |
|
| 346 |
+ "PLW0120", |
|
| 347 |
+ "PLW0127", |
|
| 348 |
+ "PLW0128", |
|
| 349 |
+ "PLW0129", |
|
| 350 |
+ "PLW0131", |
|
| 351 |
+ "PLW0133", |
|
| 352 |
+ "PLW0177", |
|
| 353 |
+ "PLW0211", |
|
| 354 |
+ "PLW0245", |
|
| 355 |
+ "PLW0406", |
|
| 356 |
+ "PLW0602", |
|
| 357 |
+ "PLW0603", |
|
| 358 |
+ "PLW0604", |
|
| 359 |
+ "PLW0642", |
|
| 360 |
+ "PLW0711", |
|
| 361 |
+ "PLW1501", |
|
| 362 |
+ "PLW1508", |
|
| 363 |
+ "PLW1509", |
|
| 364 |
+ "PLW1510", |
|
| 365 |
+ "PLW1514", |
|
| 366 |
+ "PLW1641", |
|
| 367 |
+ "PLW2101", |
|
| 368 |
+ "PLW2901", |
|
| 369 |
+ "PLW3201", |
|
| 370 |
+ "PLW3301", |
|
| 371 |
+ "PT001", |
|
| 372 |
+ "PT002", |
|
| 373 |
+ "PT003", |
|
| 374 |
+ "PT006", |
|
| 375 |
+ "PT007", |
|
| 376 |
+ "PT008", |
|
| 377 |
+ "PT009", |
|
| 378 |
+ "PT010", |
|
| 379 |
+ "PT011", |
|
| 380 |
+ "PT012", |
|
| 381 |
+ "PT013", |
|
| 382 |
+ "PT014", |
|
| 383 |
+ "PT015", |
|
| 384 |
+ "PT016", |
|
| 385 |
+ "PT017", |
|
| 386 |
+ "PT018", |
|
| 387 |
+ "PT019", |
|
| 388 |
+ "PT020", |
|
| 389 |
+ "PT021", |
|
| 390 |
+ "PT022", |
|
| 391 |
+ "PT023", |
|
| 392 |
+ "PT024", |
|
| 393 |
+ "PT025", |
|
| 394 |
+ "PT026", |
|
| 395 |
+ "PT027", |
|
| 396 |
+ "PYI001", |
|
| 397 |
+ "PYI002", |
|
| 398 |
+ "PYI003", |
|
| 399 |
+ "PYI004", |
|
| 400 |
+ "PYI005", |
|
| 401 |
+ "PYI006", |
|
| 402 |
+ "PYI007", |
|
| 403 |
+ "PYI008", |
|
| 404 |
+ "PYI009", |
|
| 405 |
+ "PYI010", |
|
| 406 |
+ "PYI011", |
|
| 407 |
+ "PYI012", |
|
| 408 |
+ "PYI013", |
|
| 409 |
+ "PYI014", |
|
| 410 |
+ "PYI015", |
|
| 411 |
+ "PYI016", |
|
| 412 |
+ "PYI017", |
|
| 413 |
+ "PYI018", |
|
| 414 |
+ "PYI019", |
|
| 415 |
+ "PYI020", |
|
| 416 |
+ "PYI021", |
|
| 417 |
+ "PYI024", |
|
| 418 |
+ "PYI025", |
|
| 419 |
+ "PYI026", |
|
| 420 |
+ "PYI029", |
|
| 421 |
+ "PYI030", |
|
| 422 |
+ "PYI032", |
|
| 423 |
+ "PYI033", |
|
| 424 |
+ "PYI034", |
|
| 425 |
+ "PYI035", |
|
| 426 |
+ "PYI036", |
|
| 427 |
+ "PYI041", |
|
| 428 |
+ "PYI042", |
|
| 429 |
+ "PYI043", |
|
| 430 |
+ "PYI044", |
|
| 431 |
+ "PYI045", |
|
| 432 |
+ "PYI046", |
|
| 433 |
+ "PYI047", |
|
| 434 |
+ "PYI048", |
|
| 435 |
+ "PYI049", |
|
| 436 |
+ "PYI050", |
|
| 437 |
+ "PYI051", |
|
| 438 |
+ "PYI052", |
|
| 439 |
+ "PYI053", |
|
| 440 |
+ "PYI054", |
|
| 441 |
+ "PYI055", |
|
| 442 |
+ "PYI056", |
|
| 443 |
+ "PYI058", |
|
| 444 |
+ "PYI059", |
|
| 445 |
+ "PYI062", |
|
| 446 |
+ "RET503", |
|
| 447 |
+ "RET504", |
|
| 448 |
+ "RET505", |
|
| 449 |
+ "RET506", |
|
| 450 |
+ "RET507", |
|
| 451 |
+ "RET508", |
|
| 452 |
+ "RSE102", |
|
| 453 |
+ "RUF001", |
|
| 454 |
+ "RUF002", |
|
| 455 |
+ "RUF003", |
|
| 456 |
+ "RUF005", |
|
| 457 |
+ "RUF006", |
|
| 458 |
+ "RUF007", |
|
| 459 |
+ "RUF008", |
|
| 460 |
+ "RUF009", |
|
| 461 |
+ "RUF010", |
|
| 462 |
+ "RUF012", |
|
| 463 |
+ "RUF013", |
|
| 464 |
+ "RUF015", |
|
| 465 |
+ "RUF016", |
|
| 466 |
+ "RUF017", |
|
| 467 |
+ "RUF018", |
|
| 468 |
+ "RUF019", |
|
| 469 |
+ "RUF020", |
|
| 470 |
+ "RUF021", |
|
| 471 |
+ "RUF022", |
|
| 472 |
+ "RUF023", |
|
| 473 |
+ "RUF024", |
|
| 474 |
+ "RUF025", |
|
| 475 |
+ "RUF026", |
|
| 476 |
+ "RUF027", |
|
| 477 |
+ "RUF028", |
|
| 478 |
+ "RUF029", |
|
| 479 |
+ "RUF100", |
|
| 480 |
+ "RUF101", |
|
| 481 |
+ "S101", |
|
| 482 |
+ "S102", |
|
| 483 |
+ "S103", |
|
| 484 |
+ "S104", |
|
| 485 |
+ "S105", |
|
| 486 |
+ "S106", |
|
| 487 |
+ "S107", |
|
| 488 |
+ "S108", |
|
| 489 |
+ "S110", |
|
| 490 |
+ "S112", |
|
| 491 |
+ "S113", |
|
| 492 |
+ "S201", |
|
| 493 |
+ "S202", |
|
| 494 |
+ "S301", |
|
| 495 |
+ "S302", |
|
| 496 |
+ "S303", |
|
| 497 |
+ "S304", |
|
| 498 |
+ "S305", |
|
| 499 |
+ "S306", |
|
| 500 |
+ "S307", |
|
| 501 |
+ "S308", |
|
| 502 |
+ "S310", |
|
| 503 |
+ "S311", |
|
| 504 |
+ "S312", |
|
| 505 |
+ "S313", |
|
| 506 |
+ "S314", |
|
| 507 |
+ "S315", |
|
| 508 |
+ "S316", |
|
| 509 |
+ "S317", |
|
| 510 |
+ "S318", |
|
| 511 |
+ "S319", |
|
| 512 |
+ "S320", |
|
| 513 |
+ "S321", |
|
| 514 |
+ "S323", |
|
| 515 |
+ "S324", |
|
| 516 |
+ "S401", |
|
| 517 |
+ "S402", |
|
| 518 |
+ "S403", |
|
| 519 |
+ "S405", |
|
| 520 |
+ "S406", |
|
| 521 |
+ "S407", |
|
| 522 |
+ "S408", |
|
| 523 |
+ "S409", |
|
| 524 |
+ "S411", |
|
| 525 |
+ "S412", |
|
| 526 |
+ "S413", |
|
| 527 |
+ "S415", |
|
| 528 |
+ "S501", |
|
| 529 |
+ "S502", |
|
| 530 |
+ "S503", |
|
| 531 |
+ "S504", |
|
| 532 |
+ "S505", |
|
| 533 |
+ "S506", |
|
| 534 |
+ "S507", |
|
| 535 |
+ "S508", |
|
| 536 |
+ "S509", |
|
| 537 |
+ "S601", |
|
| 538 |
+ "S602", |
|
| 539 |
+ "S604", |
|
| 540 |
+ "S605", |
|
| 541 |
+ "S606", |
|
| 542 |
+ "S607", |
|
| 543 |
+ "S608", |
|
| 544 |
+ "S609", |
|
| 545 |
+ "S610", |
|
| 546 |
+ "S611", |
|
| 547 |
+ "S612", |
|
| 548 |
+ "S701", |
|
| 549 |
+ "S702", |
|
| 550 |
+ "SIM101", |
|
| 551 |
+ "SIM102", |
|
| 552 |
+ "SIM103", |
|
| 553 |
+ "SIM105", |
|
| 554 |
+ "SIM107", |
|
| 555 |
+ "SIM108", |
|
| 556 |
+ "SIM109", |
|
| 557 |
+ "SIM110", |
|
| 558 |
+ "SIM112", |
|
| 559 |
+ "SIM113", |
|
| 560 |
+ "SIM114", |
|
| 561 |
+ "SIM115", |
|
| 562 |
+ "SIM116", |
|
| 563 |
+ "SIM117", |
|
| 564 |
+ "SIM118", |
|
| 565 |
+ "SIM201", |
|
| 566 |
+ "SIM202", |
|
| 567 |
+ "SIM208", |
|
| 568 |
+ "SIM210", |
|
| 569 |
+ "SIM211", |
|
| 570 |
+ "SIM212", |
|
| 571 |
+ "SIM220", |
|
| 572 |
+ "SIM221", |
|
| 573 |
+ "SIM222", |
|
| 574 |
+ "SIM223", |
|
| 575 |
+ "SIM300", |
|
| 576 |
+ "SIM910", |
|
| 577 |
+ "SIM911", |
|
| 578 |
+ "SLF001", |
|
| 579 |
+ "SLOT000", |
|
| 580 |
+ "SLOT001", |
|
| 581 |
+ "SLOT002", |
|
| 582 |
+ "T100", |
|
| 583 |
+ "T201", |
|
| 584 |
+ "T203", |
|
| 585 |
+ "TCH001", |
|
| 586 |
+ "TCH002", |
|
| 587 |
+ "TCH003", |
|
| 588 |
+ "TCH004", |
|
| 589 |
+ "TCH005", |
|
| 590 |
+ "TCH010", |
|
| 591 |
+ "TD004", |
|
| 592 |
+ "TD005", |
|
| 593 |
+ "TD006", |
|
| 594 |
+ "TD007", |
|
| 595 |
+ "TID251", |
|
| 596 |
+ "TID252", |
|
| 597 |
+ "TID253", |
|
| 598 |
+ "TRY002", |
|
| 599 |
+ "TRY003", |
|
| 600 |
+ "TRY004", |
|
| 601 |
+ "TRY201", |
|
| 602 |
+ "TRY300", |
|
| 603 |
+ "TRY301", |
|
| 604 |
+ "TRY302", |
|
| 605 |
+ "TRY400", |
|
| 606 |
+ "TRY401", |
|
| 607 |
+ "UP001", |
|
| 608 |
+ "UP003", |
|
| 609 |
+ "UP004", |
|
| 610 |
+ "UP005", |
|
| 611 |
+ "UP006", |
|
| 612 |
+ "UP007", |
|
| 613 |
+ "UP008", |
|
| 614 |
+ "UP009", |
|
| 615 |
+ "UP010", |
|
| 616 |
+ "UP011", |
|
| 617 |
+ "UP012", |
|
| 618 |
+ "UP013", |
|
| 619 |
+ "UP014", |
|
| 620 |
+ "UP015", |
|
| 621 |
+ "UP017", |
|
| 622 |
+ "UP018", |
|
| 623 |
+ "UP019", |
|
| 624 |
+ "UP020", |
|
| 625 |
+ "UP021", |
|
| 626 |
+ "UP022", |
|
| 627 |
+ "UP023", |
|
| 628 |
+ "UP024", |
|
| 629 |
+ "UP025", |
|
| 630 |
+ "UP026", |
|
| 631 |
+ "UP027", |
|
| 632 |
+ "UP028", |
|
| 633 |
+ "UP029", |
|
| 634 |
+ "UP030", |
|
| 635 |
+ "UP031", |
|
| 636 |
+ "UP032", |
|
| 637 |
+ "UP033", |
|
| 638 |
+ "UP034", |
|
| 639 |
+ "UP035", |
|
| 640 |
+ "UP036", |
|
| 641 |
+ "UP037", |
|
| 642 |
+ "UP038", |
|
| 643 |
+ "UP039", |
|
| 644 |
+ "UP040", |
|
| 645 |
+ "UP041", |
|
| 646 |
+ "UP042", |
|
| 647 |
+ "W291", |
|
| 648 |
+ "W292", |
|
| 649 |
+ "W293", |
|
| 650 |
+ "W391", |
|
| 651 |
+ "W505", |
|
| 652 |
+ "W605", |
|
| 653 |
+ "YTT101", |
|
| 654 |
+ "YTT102", |
|
| 655 |
+ "YTT103", |
|
| 656 |
+ "YTT201", |
|
| 657 |
+ "YTT202", |
|
| 658 |
+ "YTT203", |
|
| 659 |
+ "YTT204", |
|
| 660 |
+ "YTT301", |
|
| 661 |
+ "YTT302", |
|
| 662 |
+ "YTT303", |
|
| 663 |
+] |
|
| 664 |
+ |
|
| 665 |
+[lint.per-file-ignores] |
|
| 666 |
+"**/scripts/*" = [ |
|
| 667 |
+ "INP001", |
|
| 668 |
+ "T201", |
|
| 669 |
+] |
|
| 670 |
+"**/tests/**/*" = [ |
|
| 671 |
+ "PLC1901", |
|
| 672 |
+ "PLR2004", |
|
| 673 |
+ "PLR6301", |
|
| 674 |
+ "S", |
|
| 675 |
+ "TID252", |
|
| 676 |
+] |
|
| 677 |
+ |
|
| 678 |
+[lint.flake8-tidy-imports] |
|
| 679 |
+ban-relative-imports = "all" |
|
| 680 |
+ |
|
| 681 |
+[lint.isort] |
|
| 682 |
+known-first-party = ["derivepassphrase"] |
|
| 683 |
+ |
|
| 684 |
+[lint.flake8-pytest-style] |
|
| 685 |
+fixture-parentheses = false |
|
| 686 |
+mark-parentheses = false |
| ... | ... |
@@ -4,7 +4,7 @@ |
| 4 | 4 |
|
| 5 | 5 |
"""Work-alike of vault(1) – a deterministic, stateless password manager |
| 6 | 6 |
|
| 7 |
-""" |
|
| 7 |
+""" # noqa: RUF002 |
|
| 8 | 8 |
|
| 9 | 9 |
from __future__ import annotations |
| 10 | 10 |
|
| ... | ... |
@@ -24,6 +24,24 @@ __version__ = "0.1.1" |
| 24 | 24 |
|
| 25 | 25 |
class AmbiguousByteRepresentationError(ValueError): |
| 26 | 26 |
"""The object has an ambiguous byte representation.""" |
| 27 |
+ def __init__(self): |
|
| 28 |
+ super().__init__('text string has ambiguous byte representation')
|
|
| 29 |
+ |
|
| 30 |
+_CHARSETS = collections.OrderedDict([ |
|
| 31 |
+ ('lower', b'abcdefghijklmnopqrstuvwxyz'),
|
|
| 32 |
+ ('upper', b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
|
|
| 33 |
+ ('alpha', b''), # Placeholder.
|
|
| 34 |
+ ('number', b'0123456789'),
|
|
| 35 |
+ ('alphanum', b''), # Placeholder.
|
|
| 36 |
+ ('space', b' '),
|
|
| 37 |
+ ('dash', b'-_'),
|
|
| 38 |
+ ('symbol', b'!"#$%&\'()*+,./:;<=>?@[\\]^{|}~-_'),
|
|
| 39 |
+ ('all', b''), # Placeholder.
|
|
| 40 |
+]) |
|
| 41 |
+_CHARSETS['alpha'] = _CHARSETS['lower'] + _CHARSETS['upper'] |
|
| 42 |
+_CHARSETS['alphanum'] = _CHARSETS['alpha'] + _CHARSETS['number'] |
|
| 43 |
+_CHARSETS['all'] = (_CHARSETS['alphanum'] + _CHARSETS['space'] |
|
| 44 |
+ + _CHARSETS['symbol']) |
|
| 27 | 45 |
|
| 28 | 46 |
class Vault: |
| 29 | 47 |
"""A work-alike of James Coglan's vault. |
| ... | ... |
@@ -48,28 +66,13 @@ class Vault: |
| 48 | 66 |
""" |
| 49 | 67 |
_UUID = b'e87eb0f4-34cb-46b9-93ad-766c5ab063e7' |
| 50 | 68 |
"""A tag used by vault in the bit stream generation.""" |
| 51 |
- _CHARSETS: collections.OrderedDict[str, bytes] |
|
| 69 |
+ _CHARSETS = _CHARSETS |
|
| 52 | 70 |
""" |
| 53 | 71 |
Known character sets from which to draw passphrase characters. |
| 54 | 72 |
Relies on a certain, fixed order for their definition and their |
| 55 | 73 |
contents. |
| 56 | 74 |
|
| 57 | 75 |
""" |
| 58 |
- _CHARSETS = collections.OrderedDict([ |
|
| 59 |
- ('lower', b'abcdefghijklmnopqrstuvwxyz'),
|
|
| 60 |
- ('upper', b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'),
|
|
| 61 |
- ('alpha', b''), # Placeholder.
|
|
| 62 |
- ('number', b'0123456789'),
|
|
| 63 |
- ('alphanum', b''), # Placeholder.
|
|
| 64 |
- ('space', b' '),
|
|
| 65 |
- ('dash', b'-_'),
|
|
| 66 |
- ('symbol', b'!"#$%&\'()*+,./:;<=>?@[\\]^{|}~-_'),
|
|
| 67 |
- ('all', b''), # Placeholder.
|
|
| 68 |
- ]) |
|
| 69 |
- _CHARSETS['alpha'] = _CHARSETS['lower'] + _CHARSETS['upper'] |
|
| 70 |
- _CHARSETS['alphanum'] = _CHARSETS['alpha'] + _CHARSETS['number'] |
|
| 71 |
- _CHARSETS['all'] = (_CHARSETS['alphanum'] + _CHARSETS['space'] |
|
| 72 |
- + _CHARSETS['symbol']) |
|
| 73 | 76 |
|
| 74 | 77 |
def __init__( |
| 75 | 78 |
self, *, phrase: bytes | bytearray | str = b'', |
| ... | ... |
@@ -124,7 +127,7 @@ class Vault: |
| 124 | 127 |
) -> None: |
| 125 | 128 |
if not isinstance(count, int): |
| 126 | 129 |
return |
| 127 |
- elif count <= 0: |
|
| 130 |
+ if count <= 0: |
|
| 128 | 131 |
self._allowed = self._subtract(characters, self._allowed) |
| 129 | 132 |
else: |
| 130 | 133 |
for _ in range(count): |
| ... | ... |
@@ -136,9 +139,11 @@ class Vault: |
| 136 | 139 |
subtract_or_require(dash, self._CHARSETS['dash']) |
| 137 | 140 |
subtract_or_require(symbol, self._CHARSETS['symbol']) |
| 138 | 141 |
if len(self._required) > self._length: |
| 139 |
- raise ValueError('requested passphrase length too short')
|
|
| 142 |
+ msg = 'requested passphrase length too short' |
|
| 143 |
+ raise ValueError(msg) |
|
| 140 | 144 |
if not self._allowed: |
| 141 |
- raise ValueError('no allowed characters left')
|
|
| 145 |
+ msg = 'no allowed characters left' |
|
| 146 |
+ raise ValueError(msg) |
|
| 142 | 147 |
for _ in range(len(self._required), self._length): |
| 143 | 148 |
self._required.append(bytes(self._allowed)) |
| 144 | 149 |
|
| ... | ... |
@@ -167,8 +172,7 @@ class Vault: |
| 167 | 172 |
if not self._required or any(not x for x in self._required): |
| 168 | 173 |
return float('-inf')
|
| 169 | 174 |
for i, charset in enumerate(self._required): |
| 170 |
- factors.append(i + 1) |
|
| 171 |
- factors.append(len(charset)) |
|
| 175 |
+ factors.extend([i + 1, len(charset)]) |
|
| 172 | 176 |
factors.sort() |
| 173 | 177 |
return math.fsum(math.log2(f) for f in factors) |
| 174 | 178 |
|
| ... | ... |
@@ -199,10 +203,11 @@ class Vault: |
| 199 | 203 |
try: |
| 200 | 204 |
safety_factor = float(safety_factor) |
| 201 | 205 |
except TypeError as e: |
| 202 |
- raise TypeError(f'invalid safety factor: not a float: ' |
|
| 203 |
- f'{safety_factor!r}') from e
|
|
| 206 |
+ msg = f'invalid safety factor: not a float: {safety_factor!r}'
|
|
| 207 |
+ raise TypeError(msg) from e |
|
| 204 | 208 |
if not math.isfinite(safety_factor) or safety_factor < 1.0: |
| 205 |
- raise ValueError(f'invalid safety factor {safety_factor!r}')
|
|
| 209 |
+ msg = f'invalid safety factor {safety_factor!r}'
|
|
| 210 |
+ raise ValueError(msg) |
|
| 206 | 211 |
# Ensure the bound is strictly positive. |
| 207 | 212 |
entropy_bound = max(1, self._entropy()) |
| 208 | 213 |
return int(math.ceil(safety_factor * entropy_bound / 8)) |
| ... | ... |
@@ -230,8 +235,7 @@ class Vault: |
| 230 | 235 |
if isinstance(s, str): |
| 231 | 236 |
norm = unicodedata.normalize |
| 232 | 237 |
if norm('NFC', s) != norm('NFD', s):
|
| 233 |
- raise AmbiguousByteRepresentationError( |
|
| 234 |
- 'text string has ambiguous byte representation') |
|
| 238 |
+ raise AmbiguousByteRepresentationError |
|
| 235 | 239 |
return s.encode('UTF-8')
|
| 236 | 240 |
return bytes(s) |
| 237 | 241 |
|
| ... | ... |
@@ -452,11 +456,12 @@ class Vault: |
| 452 | 456 |
|
| 453 | 457 |
""" |
| 454 | 458 |
if not cls._is_suitable_ssh_key(key): |
| 455 |
- raise ValueError( |
|
| 456 |
- 'unsuitable SSH key: bad key, or signature not deterministic') |
|
| 459 |
+ msg = ('unsuitable SSH key: bad key, or '
|
|
| 460 |
+ 'signature not deterministic') |
|
| 461 |
+ raise ValueError(msg) |
|
| 457 | 462 |
with ssh_agent_client.SSHAgentClient() as client: |
| 458 | 463 |
raw_sig = client.sign(key, cls._UUID) |
| 459 |
- keytype, trailer = client.unstring_prefix(raw_sig) |
|
| 464 |
+ _keytype, trailer = client.unstring_prefix(raw_sig) |
|
| 460 | 465 |
signature_blob = client.unstring(trailer) |
| 461 | 466 |
return bytes(base64.standard_b64encode(signature_blob)) |
| 462 | 467 |
|
| ... | ... |
@@ -487,10 +492,11 @@ class Vault: |
| 487 | 492 |
allowed = (allowed if isinstance(allowed, bytearray) |
| 488 | 493 |
else bytearray(allowed)) |
| 489 | 494 |
assert_type(allowed, bytearray) |
| 495 |
+ msg_dup_characters = 'duplicate characters in set' |
|
| 490 | 496 |
if len(frozenset(allowed)) != len(allowed): |
| 491 |
- raise ValueError('duplicate characters in set')
|
|
| 497 |
+ raise ValueError(msg_dup_characters) |
|
| 492 | 498 |
if len(frozenset(charset)) != len(charset): |
| 493 |
- raise ValueError('duplicate characters in set')
|
|
| 499 |
+ raise ValueError(msg_dup_characters) |
|
| 494 | 500 |
for c in charset: |
| 495 | 501 |
try: |
| 496 | 502 |
pos = allowed.index(c) |
| ... | ... |
@@ -11,26 +11,47 @@ from __future__ import annotations |
| 11 | 11 |
import base64 |
| 12 | 12 |
import collections |
| 13 | 13 |
import contextlib |
| 14 |
+import copy |
|
| 14 | 15 |
import inspect |
| 15 | 16 |
import json |
| 16 | 17 |
import os |
| 17 |
-import pathlib |
|
| 18 | 18 |
import socket |
| 19 |
-from typing_extensions import ( |
|
| 20 |
- Any, assert_never, cast, Iterator, Sequence, TextIO, |
|
| 19 |
+from typing import ( |
|
| 20 |
+ TYPE_CHECKING, |
|
| 21 |
+ TextIO, |
|
| 22 |
+ cast, |
|
| 21 | 23 |
) |
| 22 | 24 |
|
| 23 | 25 |
import click |
| 26 |
+from typing_extensions import ( |
|
| 27 |
+ Any, |
|
| 28 |
+ assert_never, |
|
| 29 |
+) |
|
| 30 |
+ |
|
| 24 | 31 |
import derivepassphrase as dpp |
| 25 |
-from derivepassphrase import types as dpp_types |
|
| 26 | 32 |
import ssh_agent_client |
| 33 |
+from derivepassphrase import types as dpp_types |
|
| 34 |
+ |
|
| 35 |
+if TYPE_CHECKING: |
|
| 36 |
+ import pathlib |
|
| 37 |
+ from collections.abc import ( |
|
| 38 |
+ Iterator, |
|
| 39 |
+ Sequence, |
|
| 40 |
+ ) |
|
| 27 | 41 |
|
| 28 | 42 |
__author__ = dpp.__author__ |
| 29 | 43 |
__version__ = dpp.__version__ |
| 30 | 44 |
|
| 31 | 45 |
__all__ = ('derivepassphrase',)
|
| 32 | 46 |
|
| 33 |
-prog_name = 'derivepassphrase' |
|
| 47 |
+PROG_NAME = 'derivepassphrase' |
|
| 48 |
+KEY_DISPLAY_LENGTH = 30 |
|
| 49 |
+ |
|
| 50 |
+# Error messages |
|
| 51 |
+_INVALID_VAULT_CONFIG = 'Invalid vault config' |
|
| 52 |
+_AGENT_COMMUNICATION_ERROR = 'Error communicating with the SSH agent' |
|
| 53 |
+_NO_USABLE_KEYS = 'No usable SSH keys were found' |
|
| 54 |
+_EMPTY_SELECTION = 'Empty selection' |
|
| 34 | 55 |
|
| 35 | 56 |
|
| 36 | 57 |
def _config_filename() -> str | bytes | pathlib.Path: |
| ... | ... |
@@ -43,8 +64,8 @@ def _config_filename() -> str | bytes | pathlib.Path: |
| 43 | 64 |
|
| 44 | 65 |
""" |
| 45 | 66 |
path: str | bytes | pathlib.Path |
| 46 |
- path = (os.getenv(prog_name.upper() + '_PATH') |
|
| 47 |
- or click.get_app_dir(prog_name, force_posix=True)) |
|
| 67 |
+ path = (os.getenv(PROG_NAME.upper() + '_PATH') |
|
| 68 |
+ or click.get_app_dir(PROG_NAME, force_posix=True)) |
|
| 48 | 69 |
return os.path.join(path, 'settings.json') |
| 49 | 70 |
|
| 50 | 71 |
|
| ... | ... |
@@ -71,7 +92,7 @@ def _load_config() -> dpp_types.VaultConfig: |
| 71 | 92 |
with open(filename, 'rb') as fileobj: |
| 72 | 93 |
data = json.load(fileobj) |
| 73 | 94 |
if not dpp_types.is_vault_config(data): |
| 74 |
- raise ValueError('Invalid vault config')
|
|
| 95 |
+ raise ValueError(_INVALID_VAULT_CONFIG) |
|
| 75 | 96 |
return data |
| 76 | 97 |
|
| 77 | 98 |
|
| ... | ... |
@@ -94,9 +115,9 @@ def _save_config(config: dpp_types.VaultConfig, /) -> None: |
| 94 | 115 |
|
| 95 | 116 |
""" |
| 96 | 117 |
if not dpp_types.is_vault_config(config): |
| 97 |
- raise ValueError('Invalid vault config')
|
|
| 118 |
+ raise ValueError(_INVALID_VAULT_CONFIG) |
|
| 98 | 119 |
filename = _config_filename() |
| 99 |
- with open(filename, 'wt', encoding='UTF-8') as fileobj: |
|
| 120 |
+ with open(filename, 'w', encoding='UTF-8') as fileobj: |
|
| 100 | 121 |
json.dump(config, fileobj) |
| 101 | 122 |
|
| 102 | 123 |
|
| ... | ... |
@@ -150,21 +171,20 @@ def _get_suitable_ssh_keys( |
| 150 | 171 |
client_context = client |
| 151 | 172 |
case _: # pragma: no cover |
| 152 | 173 |
assert_never(conn) |
| 153 |
- raise TypeError(f'invalid connection hint: {conn!r}')
|
|
| 174 |
+ msg = f'invalid connection hint: {conn!r}'
|
|
| 175 |
+ raise TypeError(msg) |
|
| 154 | 176 |
with client_context: |
| 155 | 177 |
try: |
| 156 | 178 |
all_key_comment_pairs = list(client.list_keys()) |
| 157 | 179 |
except EOFError as e: # pragma: no cover |
| 158 |
- raise RuntimeError( |
|
| 159 |
- 'error communicating with the SSH agent' |
|
| 160 |
- ) from e |
|
| 161 |
- suitable_keys = all_key_comment_pairs[:] |
|
| 180 |
+ raise RuntimeError(_AGENT_COMMUNICATION_ERROR) from e |
|
| 181 |
+ suitable_keys = copy.copy(all_key_comment_pairs) |
|
| 162 | 182 |
for pair in all_key_comment_pairs: |
| 163 |
- key, comment = pair |
|
| 164 |
- if dpp.Vault._is_suitable_ssh_key(key): |
|
| 183 |
+ key, _comment = pair |
|
| 184 |
+ if dpp.Vault._is_suitable_ssh_key(key): # noqa: SLF001 |
|
| 165 | 185 |
yield pair |
| 166 | 186 |
if not suitable_keys: # pragma: no cover |
| 167 |
- raise IndexError('No usable SSH keys were found')
|
|
| 187 |
+ raise IndexError(_NO_USABLE_KEYS) |
|
| 168 | 188 |
|
| 169 | 189 |
|
| 170 | 190 |
def _prompt_for_selection( |
| ... | ... |
@@ -212,9 +232,8 @@ def _prompt_for_selection( |
| 212 | 232 |
err=True, type=choices, show_choices=False, |
| 213 | 233 |
show_default=False, default='') |
| 214 | 234 |
if not choice: |
| 215 |
- raise IndexError('empty selection')
|
|
| 235 |
+ raise IndexError(_EMPTY_SELECTION) |
|
| 216 | 236 |
return int(choice) - 1 |
| 217 |
- else: |
|
| 218 | 237 |
prompt_suffix = (' '
|
| 219 | 238 |
if single_choice_prompt.endswith(tuple('?.!'))
|
| 220 | 239 |
else ': ') |
| ... | ... |
@@ -223,7 +242,7 @@ def _prompt_for_selection( |
| 223 | 242 |
prompt_suffix=prompt_suffix, err=True, |
| 224 | 243 |
abort=True, default=False, show_default=False) |
| 225 | 244 |
except click.Abort: |
| 226 |
- raise IndexError('empty selection') from None
|
|
| 245 |
+ raise IndexError(_EMPTY_SELECTION) from None |
|
| 227 | 246 |
return 0 |
| 228 | 247 |
|
| 229 | 248 |
|
| ... | ... |
@@ -273,7 +292,9 @@ def _select_ssh_key( |
| 273 | 292 |
for key, comment in suitable_keys: |
| 274 | 293 |
keytype = unstring_prefix(key)[0].decode('ASCII')
|
| 275 | 294 |
key_str = base64.standard_b64encode(key).decode('ASCII')
|
| 276 |
- key_prefix = key_str if len(key_str) < 30 else key_str[:27] + '...' |
|
| 295 |
+ key_prefix = (key_str |
|
| 296 |
+ if len(key_str) < KEY_DISPLAY_LENGTH + len('...')
|
|
| 297 |
+ else key_str[:KEY_DISPLAY_LENGTH] + '...') |
|
| 277 | 298 |
comment_str = comment.decode('UTF-8', errors='replace')
|
| 278 | 299 |
key_listing.append(f'{keytype} {key_prefix} {comment_str}')
|
| 279 | 300 |
choice = _prompt_for_selection( |
| ... | ... |
@@ -317,8 +338,8 @@ class OptionGroupOption(click.Option): |
| 317 | 338 |
|
| 318 | 339 |
def __init__(self, *args, **kwargs): # type: ignore |
| 319 | 340 |
if self.__class__ == __class__: |
| 320 |
- raise NotImplementedError() |
|
| 321 |
- return super().__init__(*args, **kwargs) |
|
| 341 |
+ raise NotImplementedError |
|
| 342 |
+ super().__init__(*args, **kwargs) |
|
| 322 | 343 |
|
| 323 | 344 |
|
| 324 | 345 |
class CommandWithHelpGroups(click.Command): |
| ... | ... |
@@ -420,6 +441,8 @@ def _validate_occurrence_constraint( |
| 420 | 441 |
ctx: click.Context, param: click.Parameter, value: Any, |
| 421 | 442 |
) -> int | None: |
| 422 | 443 |
"""Check that the occurrence constraint is valid (int, 0 or larger).""" |
| 444 |
+ del ctx # Unused. |
|
| 445 |
+ del param # Unused. |
|
| 423 | 446 |
if value is None: |
| 424 | 447 |
return value |
| 425 | 448 |
if isinstance(value, int): |
| ... | ... |
@@ -428,9 +451,11 @@ def _validate_occurrence_constraint( |
| 428 | 451 |
try: |
| 429 | 452 |
int_value = int(value, 10) |
| 430 | 453 |
except ValueError as e: |
| 431 |
- raise click.BadParameter('not an integer') from e
|
|
| 454 |
+ msg = 'not an integer' |
|
| 455 |
+ raise click.BadParameter(msg) from e |
|
| 432 | 456 |
if int_value < 0: |
| 433 |
- raise click.BadParameter('not a non-negative integer')
|
|
| 457 |
+ msg = 'not a non-negative integer' |
|
| 458 |
+ raise click.BadParameter(msg) |
|
| 434 | 459 |
return int_value |
| 435 | 460 |
|
| 436 | 461 |
|
| ... | ... |
@@ -438,6 +463,8 @@ def _validate_length( |
| 438 | 463 |
ctx: click.Context, param: click.Parameter, value: Any, |
| 439 | 464 |
) -> int | None: |
| 440 | 465 |
"""Check that the length is valid (int, 1 or larger).""" |
| 466 |
+ del ctx # Unused. |
|
| 467 |
+ del param # Unused. |
|
| 441 | 468 |
if value is None: |
| 442 | 469 |
return value |
| 443 | 470 |
if isinstance(value, int): |
| ... | ... |
@@ -446,9 +473,11 @@ def _validate_length( |
| 446 | 473 |
try: |
| 447 | 474 |
int_value = int(value, 10) |
| 448 | 475 |
except ValueError as e: |
| 449 |
- raise click.BadParameter('not an integer') from e
|
|
| 476 |
+ msg = 'not an integer' |
|
| 477 |
+ raise click.BadParameter(msg) from e |
|
| 450 | 478 |
if int_value < 1: |
| 451 |
- raise click.BadParameter('not a positive integer')
|
|
| 479 |
+ msg = 'not a positive integer' |
|
| 480 |
+ raise click.BadParameter(msg) |
|
| 452 | 481 |
return int_value |
| 453 | 482 |
|
| 454 | 483 |
DEFAULT_NOTES_TEMPLATE = '''\ |
| ... | ... |
@@ -544,7 +573,7 @@ DEFAULT_NOTES_MARKER = '# - - - - - >8 - - - - -' |
| 544 | 573 |
type=click.Path(file_okay=True, allow_dash=True, exists=False), |
| 545 | 574 |
help='import saved settings from file PATH', |
| 546 | 575 |
cls=StorageManagementOption) |
| 547 |
-@click.version_option(version=dpp.__version__, prog_name=prog_name) |
|
| 576 |
+@click.version_option(version=dpp.__version__, prog_name=PROG_NAME) |
|
| 548 | 577 |
@click.argument('service', required=False)
|
| 549 | 578 |
@click.pass_context |
| 550 | 579 |
def derivepassphrase( |
| ... | ... |
@@ -674,8 +703,8 @@ def derivepassphrase( |
| 674 | 703 |
case StorageManagementOption(): |
| 675 | 704 |
group = StorageManagementOption |
| 676 | 705 |
case OptionGroupOption(): |
| 677 |
- raise AssertionError( |
|
| 678 |
- f'Unknown option group for {param!r}')
|
|
| 706 |
+ raise AssertionError( # noqa: TRY003 |
|
| 707 |
+ f'Unknown option group for {param!r}') # noqa: EM102
|
|
| 679 | 708 |
case _: |
| 680 | 709 |
group = click.Option |
| 681 | 710 |
options_in_group.setdefault(group, []).append(param) |
| ... | ... |
@@ -696,7 +725,7 @@ def derivepassphrase( |
| 696 | 725 |
return |
| 697 | 726 |
for other in incompatible: |
| 698 | 727 |
if isinstance(other, str): |
| 699 |
- other = params_by_str[other] |
|
| 728 |
+ other = params_by_str[other] # noqa: PLW2901 |
|
| 700 | 729 |
assert isinstance(other, click.Parameter) |
| 701 | 730 |
if other != param and is_param_set(other): |
| 702 | 731 |
opt_str = param.opts[0] |
| ... | ... |
@@ -709,7 +738,7 @@ def derivepassphrase( |
| 709 | 738 |
return _load_config() |
| 710 | 739 |
except FileNotFoundError: |
| 711 | 740 |
return {'services': {}}
|
| 712 |
- except Exception as e: |
|
| 741 |
+ except Exception as e: # noqa: BLE001 |
|
| 713 | 742 |
ctx.fail(f'cannot load config: {e}')
|
| 714 | 743 |
|
| 715 | 744 |
configuration: dpp_types.VaultConfig |
| ... | ... |
@@ -733,22 +762,24 @@ def derivepassphrase( |
| 733 | 762 |
for param in sv_options: |
| 734 | 763 |
if is_param_set(param) and not service: |
| 735 | 764 |
opt_str = param.opts[0] |
| 736 |
- raise click.UsageError(f'{opt_str} requires a SERVICE')
|
|
| 765 |
+ msg = f'{opt_str} requires a SERVICE'
|
|
| 766 |
+ raise click.UsageError(msg) |
|
| 737 | 767 |
for param in [params_by_str['--key'], params_by_str['--phrase']]: |
| 738 | 768 |
if ( |
| 739 | 769 |
is_param_set(param) |
| 740 | 770 |
and not (service or is_param_set(params_by_str['--config'])) |
| 741 | 771 |
): |
| 742 | 772 |
opt_str = param.opts[0] |
| 743 |
- raise click.UsageError(f'{opt_str} requires a SERVICE or --config')
|
|
| 773 |
+ msg = f'{opt_str} requires a SERVICE or --config'
|
|
| 774 |
+ raise click.UsageError(msg) |
|
| 744 | 775 |
no_sv_options = [params_by_str['--delete-globals'], |
| 745 | 776 |
params_by_str['--clear'], |
| 746 | 777 |
*options_in_group[StorageManagementOption]] |
| 747 | 778 |
for param in no_sv_options: |
| 748 | 779 |
if is_param_set(param) and service: |
| 749 | 780 |
opt_str = param.opts[0] |
| 750 |
- raise click.UsageError( |
|
| 751 |
- f'{opt_str} does not take a SERVICE argument')
|
|
| 781 |
+ msg = f'{opt_str} does not take a SERVICE argument'
|
|
| 782 |
+ raise click.UsageError(msg) |
|
| 752 | 783 |
|
| 753 | 784 |
if edit_notes: |
| 754 | 785 |
assert service is not None |
| ... | ... |
@@ -854,17 +885,14 @@ def derivepassphrase( |
| 854 | 885 |
view['phrase'] = phrase |
| 855 | 886 |
for m in view.maps: |
| 856 | 887 |
m.pop('key', '')
|
| 857 |
- if service: |
|
| 858 | 888 |
if not view.maps[0]: |
| 859 |
- raise click.UsageError('cannot update service settings '
|
|
| 860 |
- 'without actual settings') |
|
| 861 |
- else: |
|
| 889 |
+ settings_type = 'service' if service else 'global' |
|
| 890 |
+ msg = (f'cannot update {settings_type} settings without '
|
|
| 891 |
+ f'actual settings') |
|
| 892 |
+ raise click.UsageError(msg) |
|
| 893 |
+ if service: |
|
| 862 | 894 |
configuration['services'].setdefault( |
| 863 | 895 |
service, {}).update(view) # type: ignore[typeddict-item]
|
| 864 |
- else: |
|
| 865 |
- if not view.maps[0]: |
|
| 866 |
- raise click.UsageError('cannot update global settings '
|
|
| 867 |
- 'without actual settings') |
|
| 868 | 896 |
else: |
| 869 | 897 |
configuration.setdefault( |
| 870 | 898 |
'global', {}).update(view) # type: ignore[typeddict-item]
|
| ... | ... |
@@ -874,7 +902,8 @@ def derivepassphrase( |
| 874 | 902 |
_save_config(configuration) |
| 875 | 903 |
else: |
| 876 | 904 |
if not service: |
| 877 |
- raise click.UsageError('SERVICE is required')
|
|
| 905 |
+ msg = 'SERVICE is required' |
|
| 906 |
+ raise click.UsageError(msg) |
|
| 878 | 907 |
kwargs: dict[str, Any] = {k: v for k, v in settings.items()
|
| 879 | 908 |
if k in service_keys and v is not None} |
| 880 | 909 |
# If either --key or --phrase are given, use that setting. |
| ... | ... |
@@ -906,9 +935,9 @@ def derivepassphrase( |
| 906 | 935 |
elif kwargs.get('phrase'):
|
| 907 | 936 |
pass |
| 908 | 937 |
else: |
| 909 |
- raise click.UsageError( |
|
| 910 |
- 'no passphrase or key given on command-line ' |
|
| 938 |
+ msg = ('no passphrase or key given on command-line '
|
|
| 911 | 939 |
'or in configuration') |
| 940 |
+ raise click.UsageError(msg) |
|
| 912 | 941 |
vault = dpp.Vault(**kwargs) |
| 913 | 942 |
result = vault.generate(service) |
| 914 | 943 |
click.echo(result.decode('ASCII'))
|
| ... | ... |
@@ -8,8 +8,13 @@ |
| 8 | 8 |
|
| 9 | 9 |
from __future__ import annotations |
| 10 | 10 |
|
| 11 |
+from typing import TypeGuard |
|
| 12 |
+ |
|
| 11 | 13 |
from typing_extensions import ( |
| 12 |
- Any, NotRequired, Required, TypedDict, TypeGuard, |
|
| 14 |
+ Any, |
|
| 15 |
+ NotRequired, |
|
| 16 |
+ Required, |
|
| 17 |
+ TypedDict, |
|
| 13 | 18 |
) |
| 14 | 19 |
|
| 15 | 20 |
import derivepassphrase |
| ... | ... |
@@ -19,13 +19,18 @@ thoroughly documented. |
| 19 | 19 |
|
| 20 | 20 |
""" |
| 21 | 21 |
|
| 22 |
+# ruff: noqa: RUF002,RUF003 |
|
| 23 |
+ |
|
| 22 | 24 |
from __future__ import annotations |
| 23 | 25 |
|
| 24 | 26 |
import collections |
| 27 |
+from typing import TYPE_CHECKING |
|
| 25 | 28 |
|
| 26 |
-from collections.abc import Iterator, Sequence |
|
| 27 | 29 |
from typing_extensions import assert_type |
| 28 | 30 |
|
| 31 |
+if TYPE_CHECKING: |
|
| 32 |
+ from collections.abc import Iterator, Sequence |
|
| 33 |
+ |
|
| 29 | 34 |
__all__ = ('Sequin', 'SequinExhaustedError')
|
| 30 | 35 |
__author__ = "Marco Ricci <m@the13thletter.info>" |
| 31 | 36 |
__version__ = "0.1.1" |
| ... | ... |
@@ -78,6 +83,7 @@ class Sequin: |
| 78 | 83 |
range. |
| 79 | 84 |
|
| 80 | 85 |
""" |
| 86 |
+ msg = 'sequence item out of range' |
|
| 81 | 87 |
def uint8_to_bits(value): |
| 82 | 88 |
"""Yield individual bits of an 8-bit number, MSB first.""" |
| 83 | 89 |
for i in (0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01): |
| ... | ... |
@@ -86,7 +92,7 @@ class Sequin: |
| 86 | 92 |
try: |
| 87 | 93 |
sequence = tuple(sequence.encode('iso-8859-1'))
|
| 88 | 94 |
except UnicodeError as e: |
| 89 |
- raise ValueError('sequence item out of range') from e
|
|
| 95 |
+ raise ValueError(msg) from e |
|
| 90 | 96 |
else: |
| 91 | 97 |
sequence = tuple(sequence) |
| 92 | 98 |
assert_type(sequence, tuple[int, ...]) |
| ... | ... |
@@ -94,7 +100,7 @@ class Sequin: |
| 94 | 100 |
def gen() -> Iterator[int]: |
| 95 | 101 |
for num in sequence: |
| 96 | 102 |
if num not in range(2 if is_bitstring else 256): |
| 97 |
- raise ValueError('sequence item out of range')
|
|
| 103 |
+ raise ValueError(msg) |
|
| 98 | 104 |
if is_bitstring: |
| 99 | 105 |
yield num |
| 100 | 106 |
else: |
| ... | ... |
@@ -146,7 +152,7 @@ class Sequin: |
| 146 | 152 |
return () |
| 147 | 153 |
stash: collections.deque[int] = collections.deque() |
| 148 | 154 |
try: |
| 149 |
- for i in range(count): |
|
| 155 |
+ for _ in range(count): |
|
| 150 | 156 |
stash.append(seq.popleft()) |
| 151 | 157 |
except IndexError: |
| 152 | 158 |
seq.extendleft(reversed(stash)) |
| ... | ... |
@@ -183,8 +189,9 @@ class Sequin: |
| 183 | 189 |
True |
| 184 | 190 |
|
| 185 | 191 |
""" |
| 186 |
- if base < 2: |
|
| 187 |
- raise ValueError(f'invalid base: {base!r}')
|
|
| 192 |
+ if base < 2: # noqa: PLR2004 |
|
| 193 |
+ msg = f'invalid base: {base!r}'
|
|
| 194 |
+ raise ValueError(msg) |
|
| 188 | 195 |
ret = 0 |
| 189 | 196 |
allowed_range = range(base) |
| 190 | 197 |
n = len(digits) |
| ... | ... |
@@ -192,9 +199,11 @@ class Sequin: |
| 192 | 199 |
i2 = (n - 1) - i |
| 193 | 200 |
x = digits[i] |
| 194 | 201 |
if not isinstance(x, int): |
| 195 |
- raise TypeError(f'not an integer: {x!r}')
|
|
| 202 |
+ msg = f'not an integer: {x!r}'
|
|
| 203 |
+ raise TypeError(msg) |
|
| 196 | 204 |
if x not in allowed_range: |
| 197 |
- raise ValueError(f'invalid base {base!r} digit: {x!r}')
|
|
| 205 |
+ msg = f'invalid base {base!r} digit: {x!r}'
|
|
| 206 |
+ raise ValueError(msg) |
|
| 198 | 207 |
ret += (base ** i2) * x |
| 199 | 208 |
return ret |
| 200 | 209 |
|
| ... | ... |
@@ -250,11 +259,11 @@ class Sequin: |
| 250 | 259 |
SequinExhaustedError: Sequin is exhausted |
| 251 | 260 |
|
| 252 | 261 |
""" |
| 253 |
- if 2 not in self.bases: |
|
| 254 |
- raise SequinExhaustedError('Sequin is exhausted')
|
|
| 262 |
+ if 2 not in self.bases: # noqa: PLR2004 |
|
| 263 |
+ raise SequinExhaustedError |
|
| 255 | 264 |
value = self._generate_inner(n, base=2) |
| 256 | 265 |
if value == n: |
| 257 |
- raise SequinExhaustedError('Sequin is exhausted')
|
|
| 266 |
+ raise SequinExhaustedError |
|
| 258 | 267 |
return value |
| 259 | 268 |
|
| 260 | 269 |
def _generate_inner( |
| ... | ... |
@@ -319,9 +328,11 @@ class Sequin: |
| 319 | 328 |
|
| 320 | 329 |
""" |
| 321 | 330 |
if n < 1: |
| 322 |
- raise ValueError('invalid target range')
|
|
| 323 |
- if base < 2: |
|
| 324 |
- raise ValueError(f'invalid base: {base!r}')
|
|
| 331 |
+ msg = 'invalid target range' |
|
| 332 |
+ raise ValueError(msg) |
|
| 333 |
+ if base < 2: # noqa: PLR2004 |
|
| 334 |
+ msg = f'invalid base: {base!r}'
|
|
| 335 |
+ raise ValueError(msg) |
|
| 325 | 336 |
# p = base ** k, where k is the smallest integer such that |
| 326 | 337 |
# p >= n. We determine p and k inductively. |
| 327 | 338 |
p = 1 |
| ... | ... |
@@ -340,7 +351,6 @@ class Sequin: |
| 340 | 351 |
if not list_slice: |
| 341 | 352 |
if n != 1: |
| 342 | 353 |
return n |
| 343 |
- else: |
|
| 344 | 354 |
v = 0 |
| 345 | 355 |
v = self._big_endian_number(list_slice, base=base) |
| 346 | 356 |
if v > n - 1: |
| ... | ... |
@@ -365,3 +375,5 @@ class SequinExhaustedError(Exception): |
| 365 | 375 |
No more values can be generated from this sequin. |
| 366 | 376 |
|
| 367 | 377 |
""" |
| 378 |
+ def __init__(self): |
|
| 379 |
+ super().__init__('Sequin is exhausted')
|
| ... | ... |
@@ -10,20 +10,30 @@ import collections |
| 10 | 10 |
import errno |
| 11 | 11 |
import os |
| 12 | 12 |
import socket |
| 13 |
+from typing import TYPE_CHECKING |
|
| 13 | 14 |
|
| 14 |
-from collections.abc import Sequence |
|
| 15 |
-from typing_extensions import Any, Self |
|
| 15 |
+from typing_extensions import Self |
|
| 16 |
+ |
|
| 17 |
+from ssh_agent_client import types as ssh_types |
|
| 16 | 18 |
|
| 17 |
-from ssh_agent_client import types |
|
| 19 |
+if TYPE_CHECKING: |
|
| 20 |
+ import types |
|
| 21 |
+ from collections.abc import Sequence |
|
| 18 | 22 |
|
| 19 | 23 |
__all__ = ('SSHAgentClient',)
|
| 20 | 24 |
__author__ = "Marco Ricci <m@the13thletter.info>" |
| 21 | 25 |
__version__ = "0.1.1" |
| 22 | 26 |
|
| 27 |
+# In SSH bytestrings, the "length" of the byte string is stored as |
|
| 28 |
+# a 4-byte/32-bit unsigned integer at the beginning. |
|
| 29 |
+HEAD_LEN = 4 |
|
| 30 |
+ |
|
| 23 | 31 |
_socket = socket |
| 24 | 32 |
|
| 25 | 33 |
class TrailingDataError(RuntimeError): |
| 26 | 34 |
"""The result contained trailing data.""" |
| 35 |
+ def __init__(self): |
|
| 36 |
+ super().__init__('Overlong response from SSH agent')
|
|
| 27 | 37 |
|
| 28 | 38 |
class SSHAgentClient: |
| 29 | 39 |
"""A bare-bones SSH agent client supporting signing and key listing. |
| ... | ... |
@@ -76,7 +86,8 @@ class SSHAgentClient: |
| 76 | 86 |
if e.errno != errno.ENOTCONN: # pragma: no cover |
| 77 | 87 |
raise |
| 78 | 88 |
if 'SSH_AUTH_SOCK' not in os.environ: |
| 79 |
- raise KeyError('SSH_AUTH_SOCK environment variable')
|
|
| 89 |
+ msg = 'SSH_AUTH_SOCK environment variable' |
|
| 90 |
+ raise KeyError(msg) from None |
|
| 80 | 91 |
ssh_auth_sock = os.environ['SSH_AUTH_SOCK'] |
| 81 | 92 |
self._connection.settimeout(timeout) |
| 82 | 93 |
self._connection.connect(ssh_auth_sock) |
| ... | ... |
@@ -87,7 +98,10 @@ class SSHAgentClient: |
| 87 | 98 |
return self |
| 88 | 99 |
|
| 89 | 100 |
def __exit__( |
| 90 |
- self, exc_type: Any, exc_val: Any, exc_tb: Any |
|
| 101 |
+ self, |
|
| 102 |
+ exc_type: type[BaseException] | None, |
|
| 103 |
+ exc_val: BaseException | None, |
|
| 104 |
+ exc_tb: types.TracebackType | None, |
|
| 91 | 105 |
) -> bool: |
| 92 | 106 |
"""Close socket connection upon context manager completion.""" |
| 93 | 107 |
return bool( |
| ... | ... |
@@ -136,9 +150,10 @@ class SSHAgentClient: |
| 136 | 150 |
ret = bytearray() |
| 137 | 151 |
ret.extend(cls.uint32(len(payload))) |
| 138 | 152 |
ret.extend(payload) |
| 139 |
- return ret |
|
| 140 | 153 |
except Exception as e: |
| 141 |
- raise TypeError('invalid payload type') from e
|
|
| 154 |
+ msg = 'invalid payload type' |
|
| 155 |
+ raise TypeError(msg) from e |
|
| 156 |
+ return ret |
|
| 142 | 157 |
|
| 143 | 158 |
@classmethod |
| 144 | 159 |
def unstring(cls, bytestring: bytes | bytearray, /) -> bytes | bytearray: |
| ... | ... |
@@ -163,11 +178,14 @@ class SSHAgentClient: |
| 163 | 178 |
|
| 164 | 179 |
""" |
| 165 | 180 |
n = len(bytestring) |
| 166 |
- if n < 4: |
|
| 167 |
- raise ValueError('malformed SSH byte string')
|
|
| 168 |
- elif n != 4 + int.from_bytes(bytestring[:4], 'big', signed=False): |
|
| 169 |
- raise ValueError('malformed SSH byte string')
|
|
| 170 |
- return bytestring[4:] |
|
| 181 |
+ msg = 'malformed SSH byte string' |
|
| 182 |
+ if ( |
|
| 183 |
+ n < HEAD_LEN |
|
| 184 |
+ or n != HEAD_LEN + int.from_bytes(bytestring[:HEAD_LEN], 'big', |
|
| 185 |
+ signed=False) |
|
| 186 |
+ ): |
|
| 187 |
+ raise ValueError(msg) |
|
| 188 |
+ return bytestring[HEAD_LEN:] |
|
| 171 | 189 |
|
| 172 | 190 |
@classmethod |
| 173 | 191 |
def unstring_prefix( |
| ... | ... |
@@ -201,12 +219,13 @@ class SSHAgentClient: |
| 201 | 219 |
|
| 202 | 220 |
""" |
| 203 | 221 |
n = len(bytestring) |
| 204 |
- if n < 4: |
|
| 205 |
- raise ValueError('malformed SSH byte string')
|
|
| 206 |
- m = int.from_bytes(bytestring[:4], 'big', signed=False) |
|
| 207 |
- if m + 4 > n: |
|
| 208 |
- raise ValueError('malformed SSH byte string')
|
|
| 209 |
- return (bytestring[4:m + 4], bytestring[m + 4:]) |
|
| 222 |
+ msg = 'malformed SSH byte string' |
|
| 223 |
+ if n < HEAD_LEN: |
|
| 224 |
+ raise ValueError(msg) |
|
| 225 |
+ m = int.from_bytes(bytestring[:HEAD_LEN], 'big', signed=False) |
|
| 226 |
+ if m + HEAD_LEN > n: |
|
| 227 |
+ raise ValueError(msg) |
|
| 228 |
+ return (bytestring[HEAD_LEN:m + HEAD_LEN], bytestring[m + HEAD_LEN:]) |
|
| 210 | 229 |
|
| 211 | 230 |
def request( |
| 212 | 231 |
self, code: int, payload: bytes | bytearray, / |
| ... | ... |
@@ -236,16 +255,18 @@ class SSHAgentClient: |
| 236 | 255 |
request_message = bytearray([code]) |
| 237 | 256 |
request_message.extend(payload) |
| 238 | 257 |
self._connection.sendall(self.string(request_message)) |
| 239 |
- chunk = self._connection.recv(4) |
|
| 240 |
- if len(chunk) < 4: |
|
| 241 |
- raise EOFError('cannot read response length')
|
|
| 258 |
+ chunk = self._connection.recv(HEAD_LEN) |
|
| 259 |
+ if len(chunk) < HEAD_LEN: |
|
| 260 |
+ msg = 'cannot read response length' |
|
| 261 |
+ raise EOFError(msg) |
|
| 242 | 262 |
response_length = int.from_bytes(chunk, 'big', signed=False) |
| 243 | 263 |
response = self._connection.recv(response_length) |
| 244 | 264 |
if len(response) < response_length: |
| 245 |
- raise EOFError('truncated response from SSH agent')
|
|
| 265 |
+ msg = 'truncated response from SSH agent' |
|
| 266 |
+ raise EOFError(msg) |
|
| 246 | 267 |
return response[0], response[1:] |
| 247 | 268 |
|
| 248 |
- def list_keys(self) -> Sequence[types.KeyCommentPair]: |
|
| 269 |
+ def list_keys(self) -> Sequence[ssh_types.KeyCommentPair]: |
|
| 249 | 270 |
"""Request a list of keys known to the SSH agent. |
| 250 | 271 |
|
| 251 | 272 |
Returns: |
| ... | ... |
@@ -261,37 +282,35 @@ class SSHAgentClient: |
| 261 | 282 |
|
| 262 | 283 |
""" |
| 263 | 284 |
response_code, response = self.request( |
| 264 |
- types.SSH_AGENTC.REQUEST_IDENTITIES.value, b'') |
|
| 265 |
- if response_code != types.SSH_AGENT.IDENTITIES_ANSWER.value: |
|
| 266 |
- raise RuntimeError( |
|
| 267 |
- f'error return from SSH agent: ' |
|
| 268 |
- f'{response_code = }, {response = }'
|
|
| 269 |
- ) |
|
| 285 |
+ ssh_types.SSH_AGENTC.REQUEST_IDENTITIES.value, b'') |
|
| 286 |
+ if response_code != ssh_types.SSH_AGENT.IDENTITIES_ANSWER.value: |
|
| 287 |
+ msg = (f'error return from SSH agent: ' |
|
| 288 |
+ f'{response_code = }, {response = }')
|
|
| 289 |
+ raise RuntimeError(msg) |
|
| 270 | 290 |
response_stream = collections.deque(response) |
| 271 | 291 |
def shift(num: int) -> bytes: |
| 272 |
- buf = collections.deque(bytes()) |
|
| 273 |
- for i in range(num): |
|
| 292 |
+ buf = collections.deque(b'') |
|
| 293 |
+ for _ in range(num): |
|
| 274 | 294 |
try: |
| 275 | 295 |
val = response_stream.popleft() |
| 276 | 296 |
except IndexError: |
| 277 | 297 |
response_stream.extendleft(reversed(buf)) |
| 278 |
- raise EOFError( |
|
| 279 |
- 'truncated response from SSH agent' |
|
| 280 |
- ) from None |
|
| 298 |
+ msg = 'truncated response from SSH agent' |
|
| 299 |
+ raise EOFError(msg) from None |
|
| 281 | 300 |
buf.append(val) |
| 282 | 301 |
return bytes(buf) |
| 283 | 302 |
key_count = int.from_bytes(shift(4), 'big') |
| 284 |
- keys: collections.deque[types.KeyCommentPair] |
|
| 303 |
+ keys: collections.deque[ssh_types.KeyCommentPair] |
|
| 285 | 304 |
keys = collections.deque() |
| 286 |
- for i in range(key_count): |
|
| 305 |
+ for _ in range(key_count): |
|
| 287 | 306 |
key_size = int.from_bytes(shift(4), 'big') |
| 288 | 307 |
key = shift(key_size) |
| 289 | 308 |
comment_size = int.from_bytes(shift(4), 'big') |
| 290 | 309 |
comment = shift(comment_size) |
| 291 | 310 |
# Both `key` and `comment` are not wrapped as SSH strings. |
| 292 |
- keys.append(types.KeyCommentPair(key, comment)) |
|
| 311 |
+ keys.append(ssh_types.KeyCommentPair(key, comment)) |
|
| 293 | 312 |
if response_stream: |
| 294 |
- raise TrailingDataError('overlong response from SSH agent')
|
|
| 313 |
+ raise TrailingDataError |
|
| 295 | 314 |
return keys |
| 296 | 315 |
|
| 297 | 316 |
def sign( |
| ... | ... |
@@ -336,14 +355,14 @@ class SSHAgentClient: |
| 336 | 355 |
if check_if_key_loaded: |
| 337 | 356 |
loaded_keys = frozenset({pair.key for pair in self.list_keys()})
|
| 338 | 357 |
if bytes(key) not in loaded_keys: |
| 339 |
- raise KeyError('target SSH key not loaded into agent')
|
|
| 358 |
+ msg = 'target SSH key not loaded into agent' |
|
| 359 |
+ raise KeyError(msg) |
|
| 340 | 360 |
request_data = bytearray(self.string(key)) |
| 341 | 361 |
request_data.extend(self.string(payload)) |
| 342 | 362 |
request_data.extend(self.uint32(flags)) |
| 343 | 363 |
response_code, response = self.request( |
| 344 |
- types.SSH_AGENTC.SIGN_REQUEST.value, request_data) |
|
| 345 |
- if response_code != types.SSH_AGENT.SIGN_RESPONSE.value: |
|
| 346 |
- raise RuntimeError( |
|
| 347 |
- f'signing data failed: {response_code = }, {response = }'
|
|
| 348 |
- ) |
|
| 364 |
+ ssh_types.SSH_AGENTC.SIGN_REQUEST.value, request_data) |
|
| 365 |
+ if response_code != ssh_types.SSH_AGENT.SIGN_RESPONSE.value: |
|
| 366 |
+ msg = f'signing data failed: {response_code = }, {response = }'
|
|
| 367 |
+ raise RuntimeError(msg) |
|
| 349 | 368 |
return self.unstring(response) |
| ... | ... |
@@ -7,10 +7,9 @@ |
| 7 | 7 |
from __future__ import annotations |
| 8 | 8 |
|
| 9 | 9 |
import enum |
| 10 |
- |
|
| 11 | 10 |
from typing import NamedTuple |
| 12 | 11 |
|
| 13 |
-__all__ = ('KeyCommentPair', 'SSH_AGENT', 'SSH_AGENTC')
|
|
| 12 |
+__all__ = ('SSH_AGENT', 'SSH_AGENTC', 'KeyCommentPair')
|
|
| 14 | 13 |
|
| 15 | 14 |
class KeyCommentPair(NamedTuple): |
| 16 | 15 |
"""SSH key plus comment pair. For typing purposes. |
| ... | ... |
@@ -23,7 +22,7 @@ class KeyCommentPair(NamedTuple): |
| 23 | 22 |
key: bytes | bytearray |
| 24 | 23 |
comment: bytes | bytearray |
| 25 | 24 |
|
| 26 |
-class SSH_AGENTC(enum.Enum): |
|
| 25 |
+class SSH_AGENTC(enum.Enum): # noqa: N801 |
|
| 27 | 26 |
"""SSH agent protocol numbers: client requests. |
| 28 | 27 |
|
| 29 | 28 |
Attributes: |
| ... | ... |
@@ -36,7 +35,7 @@ class SSH_AGENTC(enum.Enum): |
| 36 | 35 |
REQUEST_IDENTITIES: int = 11 |
| 37 | 36 |
SIGN_REQUEST: int = 13 |
| 38 | 37 |
|
| 39 |
-class SSH_AGENT(enum.Enum): |
|
| 38 |
+class SSH_AGENT(enum.Enum): # noqa: N801 |
|
| 40 | 39 |
"""SSH agent protocol numbers: server replies. |
| 41 | 40 |
|
| 42 | 41 |
Attributes: |
| ... | ... |
@@ -5,24 +5,27 @@ |
| 5 | 5 |
from __future__ import annotations |
| 6 | 6 |
|
| 7 | 7 |
import base64 |
| 8 |
-from collections.abc import Iterator, Mapping |
|
| 9 | 8 |
import contextlib |
| 10 | 9 |
import json |
| 11 | 10 |
import os |
| 12 | 11 |
from typing import TYPE_CHECKING |
| 13 |
-from typing_extensions import Any, TypedDict |
|
| 14 | 12 |
|
| 15 |
-import click.testing |
|
| 13 |
+import pytest |
|
| 14 |
+ |
|
| 16 | 15 |
import derivepassphrase |
| 17 | 16 |
import derivepassphrase.cli |
| 18 | 17 |
import derivepassphrase.types |
| 19 |
-import pytest |
|
| 20 | 18 |
import ssh_agent_client |
| 21 | 19 |
import ssh_agent_client.types |
| 22 | 20 |
|
| 23 | 21 |
__all__ = () |
| 24 | 22 |
|
| 25 | 23 |
if TYPE_CHECKING: |
| 24 |
+ from collections.abc import Iterator, Mapping |
|
| 25 |
+ |
|
| 26 |
+ import click.testing |
|
| 27 |
+ from typing_extensions import Any, TypedDict |
|
| 28 |
+ |
|
| 26 | 29 |
class SSHTestKey(TypedDict): |
| 27 | 30 |
private_key: bytes |
| 28 | 31 |
public_key: bytes | str |
| ... | ... |
@@ -343,7 +346,8 @@ skip_if_no_agent = pytest.mark.skipif( |
| 343 | 346 |
def list_keys( |
| 344 | 347 |
self: Any = None, |
| 345 | 348 |
) -> list[ssh_agent_client.types.KeyCommentPair]: |
| 346 |
- Pair = ssh_agent_client.types.KeyCommentPair |
|
| 349 |
+ del self # Unused. |
|
| 350 |
+ Pair = ssh_agent_client.types.KeyCommentPair # noqa: N806 |
|
| 347 | 351 |
list1 = [Pair(value['public_key_data'], f'{key} test key'.encode('ASCII'))
|
| 348 | 352 |
for key, value in SUPPORTED_KEYS.items()] |
| 349 | 353 |
list2 = [Pair(value['public_key_data'], f'{key} test key'.encode('ASCII'))
|
| ... | ... |
@@ -353,7 +357,8 @@ def list_keys( |
| 353 | 357 |
def list_keys_singleton( |
| 354 | 358 |
self: Any = None, |
| 355 | 359 |
) -> list[ssh_agent_client.types.KeyCommentPair]: |
| 356 |
- Pair = ssh_agent_client.types.KeyCommentPair |
|
| 360 |
+ del self # Unused. |
|
| 361 |
+ Pair = ssh_agent_client.types.KeyCommentPair # noqa: N806 |
|
| 357 | 362 |
list1 = [Pair(value['public_key_data'], f'{key} test key'.encode('ASCII'))
|
| 358 | 363 |
for key, value in SUPPORTED_KEYS.items()] |
| 359 | 364 |
return list1[:1] |
| ... | ... |
@@ -361,6 +366,7 @@ def list_keys_singleton( |
| 361 | 366 |
def suitable_ssh_keys( |
| 362 | 367 |
conn: Any |
| 363 | 368 |
) -> Iterator[ssh_agent_client.types.KeyCommentPair]: |
| 369 |
+ del conn # Unused. |
|
| 364 | 370 |
yield from [ |
| 365 | 371 |
ssh_agent_client.types.KeyCommentPair(DUMMY_KEY1, b'no comment'), |
| 366 | 372 |
ssh_agent_client.types.KeyCommentPair(DUMMY_KEY2, b'a comment'), |
| ... | ... |
@@ -375,17 +381,23 @@ def phrase_from_key(key: bytes) -> bytes: |
| 375 | 381 |
def isolated_config( |
| 376 | 382 |
monkeypatch: Any, runner: click.testing.CliRunner, config: Any, |
| 377 | 383 |
): |
| 378 |
- prog_name = derivepassphrase.cli.prog_name |
|
| 384 |
+ prog_name = derivepassphrase.cli.PROG_NAME |
|
| 379 | 385 |
env_name = prog_name.replace(' ', '_').upper() + '_PATH'
|
| 380 | 386 |
with runner.isolated_filesystem(): |
| 381 | 387 |
monkeypatch.setenv('HOME', os.getcwd())
|
| 382 | 388 |
monkeypatch.setenv('USERPROFILE', os.getcwd())
|
| 383 | 389 |
monkeypatch.delenv(env_name, raising=False) |
| 384 |
- os.makedirs(os.path.dirname(derivepassphrase.cli._config_filename()), |
|
| 385 |
- exist_ok=True) |
|
| 386 |
- with open(derivepassphrase.cli._config_filename(), 'wt') as outfile: |
|
| 390 |
+ os.makedirs( |
|
| 391 |
+ os.path.dirname(derivepassphrase.cli._config_filename()), |
|
| 392 |
+ exist_ok=True, |
|
| 393 |
+ ) |
|
| 394 |
+ with open( |
|
| 395 |
+ derivepassphrase.cli._config_filename(), |
|
| 396 |
+ 'w', encoding='UTF-8', |
|
| 397 |
+ ) as outfile: |
|
| 387 | 398 |
json.dump(config, outfile) |
| 388 | 399 |
yield |
| 389 | 400 |
|
| 390 | 401 |
def auto_prompt(*args: Any, **kwargs: Any) -> str: |
| 402 |
+ del args, kwargs # Unused. |
|
| 391 | 403 |
return DUMMY_PASSPHRASE.decode('UTF-8')
|
| ... | ... |
@@ -9,9 +9,10 @@ from __future__ import annotations |
| 9 | 9 |
import math |
| 10 | 10 |
from typing import Any |
| 11 | 11 |
|
| 12 |
-import derivepassphrase |
|
| 13 | 12 |
import pytest |
| 14 | 13 |
|
| 14 |
+import derivepassphrase |
|
| 15 |
+ |
|
| 15 | 16 |
Vault = derivepassphrase.Vault |
| 16 | 17 |
|
| 17 | 18 |
class TestVault: |
| ... | ... |
@@ -106,7 +107,7 @@ class TestVault: |
| 106 | 107 |
def test_219_very_limited_character_set(self): |
| 107 | 108 |
generated = Vault(phrase=b'', length=24, lower=0, upper=0, |
| 108 | 109 |
space=0, symbol=0).generate('testing')
|
| 109 |
- assert b'763252593304946694588866' == generated |
|
| 110 |
+ assert generated == b'763252593304946694588866' |
|
| 110 | 111 |
|
| 111 | 112 |
def test_220_character_set_subtraction(self): |
| 112 | 113 |
assert Vault._subtract(b'be', b'abcdef') == bytearray(b'acdf') |
| ... | ... |
@@ -152,21 +153,21 @@ class TestVault: |
| 152 | 153 |
v = Vault(phrase=self.phrase) |
| 153 | 154 |
monkeypatch.setattr(v, |
| 154 | 155 |
'_estimate_sufficient_hash_length', |
| 155 |
- lambda *args, **kwargs: 1) |
|
| 156 |
+ lambda *args, **kwargs: 1) # noqa: ARG005 |
|
| 156 | 157 |
assert v._estimate_sufficient_hash_length() < len(self.phrase) |
| 157 | 158 |
assert v.generate(service) == expected |
| 158 | 159 |
|
| 159 | 160 |
@pytest.mark.parametrize(['s', 'raises'], [ |
| 160 | 161 |
('ñ', True), ('Düsseldorf', True),
|
| 161 | 162 |
('liberté, egalité, fraternité', True), ('ASCII', False),
|
| 162 |
- ('Düsseldorf'.encode('UTF-8'), False),
|
|
| 163 |
+ (b'D\xc3\xbcsseldorf', False), |
|
| 163 | 164 |
(bytearray([2, 3, 5, 7, 11, 13]), False), |
| 164 | 165 |
]) |
| 165 | 166 |
def test_224_binary_strings( |
| 166 | 167 |
self, s: str | bytes | bytearray, raises: bool |
| 167 | 168 |
) -> None: |
| 168 | 169 |
binstr = derivepassphrase.Vault._get_binary_string |
| 169 |
- AmbiguousByteRepresentationError = ( |
|
| 170 |
+ AmbiguousByteRepresentationError = ( # noqa: N806 |
|
| 170 | 171 |
derivepassphrase.AmbiguousByteRepresentationError |
| 171 | 172 |
) |
| 172 | 173 |
if raises: |
| ... | ... |
@@ -4,18 +4,25 @@ |
| 4 | 4 |
|
| 5 | 5 |
from __future__ import annotations |
| 6 | 6 |
|
| 7 |
-from collections.abc import Callable |
|
| 7 |
+import contextlib |
|
| 8 | 8 |
import json |
| 9 | 9 |
import os |
| 10 | 10 |
import socket |
| 11 |
-from typing_extensions import Any, cast, NamedTuple |
|
| 11 |
+from typing import TYPE_CHECKING, cast |
|
| 12 | 12 |
|
| 13 | 13 |
import click.testing |
| 14 |
-import derivepassphrase as dpp |
|
| 15 |
-import derivepassphrase.cli as cli |
|
| 16 |
-import ssh_agent_client.types |
|
| 17 | 14 |
import pytest |
| 15 |
+from typing_extensions import NamedTuple |
|
| 16 |
+ |
|
| 17 |
+import derivepassphrase as dpp |
|
| 18 |
+import ssh_agent_client |
|
| 18 | 19 |
import tests |
| 20 |
+from derivepassphrase import cli |
|
| 21 |
+ |
|
| 22 |
+if TYPE_CHECKING: |
|
| 23 |
+ from collections.abc import Callable |
|
| 24 |
+ |
|
| 25 |
+ from typing_extensions import Any |
|
| 19 | 26 |
|
| 20 | 27 |
DUMMY_SERVICE = tests.DUMMY_SERVICE |
| 21 | 28 |
DUMMY_PASSPHRASE = tests.DUMMY_PASSPHRASE |
| ... | ... |
@@ -64,7 +72,7 @@ STORAGE_OPTIONS: list[tuple[str, ...]] = [ |
| 64 | 72 |
] |
| 65 | 73 |
INCOMPATIBLE: dict[tuple[str, ...], IncompatibleConfiguration] = {
|
| 66 | 74 |
('--phrase',): IncompatibleConfiguration(
|
| 67 |
- [('--key',)] + CONFIGURATION_COMMANDS + STORAGE_OPTIONS,
|
|
| 75 |
+ [('--key',), *CONFIGURATION_COMMANDS, *STORAGE_OPTIONS],
|
|
| 68 | 76 |
True, DUMMY_PASSPHRASE), |
| 69 | 77 |
('--key',): IncompatibleConfiguration(
|
| 70 | 78 |
CONFIGURATION_COMMANDS + STORAGE_OPTIONS, |
| ... | ... |
@@ -95,16 +103,16 @@ INCOMPATIBLE: dict[tuple[str, ...], IncompatibleConfiguration] = {
|
| 95 | 103 |
True, DUMMY_PASSPHRASE), |
| 96 | 104 |
('--notes',): IncompatibleConfiguration(
|
| 97 | 105 |
[('--config',), ('--delete',), ('--delete-globals',),
|
| 98 |
- ('--clear',)] + STORAGE_OPTIONS,
|
|
| 106 |
+ ('--clear',), *STORAGE_OPTIONS],
|
|
| 99 | 107 |
True, None), |
| 100 | 108 |
('--config', '-p'): IncompatibleConfiguration(
|
| 101 | 109 |
[('--delete',), ('--delete-globals',),
|
| 102 |
- ('--clear',)] + STORAGE_OPTIONS,
|
|
| 110 |
+ ('--clear',), *STORAGE_OPTIONS],
|
|
| 103 | 111 |
None, DUMMY_PASSPHRASE), |
| 104 | 112 |
('--delete',): IncompatibleConfiguration(
|
| 105 |
- [('--delete-globals',), ('--clear',)] + STORAGE_OPTIONS, True, None),
|
|
| 113 |
+ [('--delete-globals',), ('--clear',), *STORAGE_OPTIONS], True, None),
|
|
| 106 | 114 |
('--delete-globals',): IncompatibleConfiguration(
|
| 107 |
- [('--clear',)] + STORAGE_OPTIONS, False, None),
|
|
| 115 |
+ [('--clear',), *STORAGE_OPTIONS], False, None),
|
|
| 108 | 116 |
('--clear',): IncompatibleConfiguration(STORAGE_OPTIONS, False, None),
|
| 109 | 117 |
('--export', '-'): IncompatibleConfiguration(
|
| 110 | 118 |
[('--import', '-')], False, None),
|
| ... | ... |
@@ -163,9 +171,9 @@ class TestCLI: |
| 163 | 171 |
'Option group epilog not printed.' |
| 164 | 172 |
) |
| 165 | 173 |
|
| 166 |
- @pytest.mark.parametrize(['charset_name'], |
|
| 167 |
- [('lower',), ('upper',), ('number',), ('space',),
|
|
| 168 |
- ('dash',), ('symbol',)])
|
|
| 174 |
+ @pytest.mark.parametrize('charset_name',
|
|
| 175 |
+ ['lower', 'upper', 'number', 'space', |
|
| 176 |
+ 'dash', 'symbol']) |
|
| 169 | 177 |
def test_201_disable_character_set( |
| 170 | 178 |
self, monkeypatch: Any, charset_name: str |
| 171 | 179 |
) -> None: |
| ... | ... |
@@ -207,7 +215,7 @@ class TestCLI: |
| 207 | 215 |
f'at position {i}: {result.stdout!r}'
|
| 208 | 216 |
) |
| 209 | 217 |
|
| 210 |
- @pytest.mark.parametrize(['config'], [ |
|
| 218 |
+ @pytest.mark.parametrize('config', [
|
|
| 211 | 219 |
pytest.param({'global': {'key': DUMMY_KEY1_B64},
|
| 212 | 220 |
'services': {DUMMY_SERVICE: DUMMY_CONFIG_SETTINGS}},
|
| 213 | 221 |
id='global'), |
| ... | ... |
@@ -287,10 +295,10 @@ class TestCLI: |
| 287 | 295 |
'program generated unexpected result (wrong settings?)' |
| 288 | 296 |
) |
| 289 | 297 |
|
| 290 |
- @pytest.mark.parametrize(['option'], |
|
| 291 |
- [('--lower',), ('--upper',), ('--number',),
|
|
| 292 |
- ('--space',), ('--dash',), ('--symbol',),
|
|
| 293 |
- ('--repeat',), ('--length',)])
|
|
| 298 |
+ @pytest.mark.parametrize('option',
|
|
| 299 |
+ ['--lower', '--upper', '--number', |
|
| 300 |
+ '--space', '--dash', '--symbol', |
|
| 301 |
+ '--repeat', '--length']) |
|
| 294 | 302 |
def test_210_invalid_argument_range(self, option: str) -> None: |
| 295 | 303 |
runner = click.testing.CliRunner(mix_stderr=False) |
| 296 | 304 |
value: str | int |
| ... | ... |
@@ -326,7 +334,7 @@ class TestCLI: |
| 326 | 334 |
'services': {}}):
|
| 327 | 335 |
result = runner.invoke(cli.derivepassphrase, |
| 328 | 336 |
options if service |
| 329 |
- else options + [DUMMY_SERVICE], |
|
| 337 |
+ else [*options, DUMMY_SERVICE], |
|
| 330 | 338 |
input=input, catch_exceptions=False) |
| 331 | 339 |
if service is not None: |
| 332 | 340 |
assert result.exit_code > 0, ( |
| ... | ... |
@@ -351,7 +359,7 @@ class TestCLI: |
| 351 | 359 |
monkeypatch.setattr(cli, '_prompt_for_passphrase', |
| 352 | 360 |
tests.auto_prompt) |
| 353 | 361 |
result = runner.invoke(cli.derivepassphrase, |
| 354 |
- options + [DUMMY_SERVICE] |
|
| 362 |
+ [*options, DUMMY_SERVICE] |
|
| 355 | 363 |
if service else options, |
| 356 | 364 |
input=input, catch_exceptions=False) |
| 357 | 365 |
assert (result.exit_code, result.stderr_bytes) == (0, b''), ( |
| ... | ... |
@@ -359,16 +367,16 @@ class TestCLI: |
| 359 | 367 |
) |
| 360 | 368 |
|
| 361 | 369 |
@pytest.mark.parametrize( |
| 362 |
- ['options', 'service', 'input'], |
|
| 363 |
- [(o.options, o.needs_service, o.input) |
|
| 370 |
+ ['options', 'service'], |
|
| 371 |
+ [(o.options, o.needs_service) |
|
| 364 | 372 |
for o in INTERESTING_OPTION_COMBINATIONS if o.incompatible], |
| 365 | 373 |
) |
| 366 | 374 |
def test_212_incompatible_options( |
| 367 |
- self, options: list[str], service: bool | None, input: bytes | None, |
|
| 375 |
+ self, options: list[str], service: bool | None, |
|
| 368 | 376 |
) -> None: |
| 369 | 377 |
runner = click.testing.CliRunner(mix_stderr=False) |
| 370 | 378 |
result = runner.invoke(cli.derivepassphrase, |
| 371 |
- options + [DUMMY_SERVICE] if service |
|
| 379 |
+ [*options, DUMMY_SERVICE] if service |
|
| 372 | 380 |
else options, |
| 373 | 381 |
input=DUMMY_PASSPHRASE, catch_exceptions=False) |
| 374 | 382 |
assert result.exit_code > 0, ( |
| ... | ... |
@@ -427,7 +435,8 @@ class TestCLI: |
| 427 | 435 |
# ourselves afterwards, inside the context. |
| 428 | 436 |
with tests.isolated_config(monkeypatch=monkeypatch, runner=runner, |
| 429 | 437 |
config={'services': {}}):
|
| 430 |
- with open(cli._config_filename(), 'wt') as outfile: |
|
| 438 |
+ with open(cli._config_filename(), 'w', |
|
| 439 |
+ encoding='UTF-8') as outfile: |
|
| 431 | 440 |
print('This string is not valid JSON.', file=outfile)
|
| 432 | 441 |
dname = os.path.dirname(cli._config_filename()) |
| 433 | 442 |
result = runner.invoke( |
| ... | ... |
@@ -449,10 +458,8 @@ class TestCLI: |
| 449 | 458 |
runner = click.testing.CliRunner(mix_stderr=False) |
| 450 | 459 |
with tests.isolated_config(monkeypatch=monkeypatch, runner=runner, |
| 451 | 460 |
config={'services': {}}):
|
| 452 |
- try: |
|
| 461 |
+ with contextlib.suppress(FileNotFoundError): |
|
| 453 | 462 |
os.remove(cli._config_filename()) |
| 454 |
- except FileNotFoundError: # pragma: no cover |
|
| 455 |
- pass |
|
| 456 | 463 |
result = runner.invoke(cli.derivepassphrase, ['--export', '-'], |
| 457 | 464 |
catch_exceptions=False) |
| 458 | 465 |
assert (result.exit_code, result.stderr_bytes) == (0, b''), ( |
| ... | ... |
@@ -483,10 +490,8 @@ class TestCLI: |
| 483 | 490 |
runner = click.testing.CliRunner(mix_stderr=False) |
| 484 | 491 |
with tests.isolated_config(monkeypatch=monkeypatch, runner=runner, |
| 485 | 492 |
config={'services': {}}):
|
| 486 |
- try: |
|
| 493 |
+ with contextlib.suppress(FileNotFoundError): |
|
| 487 | 494 |
os.remove(cli._config_filename()) |
| 488 |
- except FileNotFoundError: # pragma: no cover |
|
| 489 |
- pass |
|
| 490 | 495 |
os.makedirs(cli._config_filename()) |
| 491 | 496 |
result = runner.invoke(cli.derivepassphrase, ['--export', '-'], |
| 492 | 497 |
input=b'null', catch_exceptions=False) |
| ... | ... |
@@ -531,13 +536,13 @@ contents go here |
| 531 | 536 |
config={'global': {'phrase': 'abc'},
|
| 532 | 537 |
'services': {}}):
|
| 533 | 538 |
monkeypatch.setattr(click, 'edit', |
| 534 |
- lambda *a, **kw: edit_result) |
|
| 539 |
+ lambda *a, **kw: edit_result) # noqa: ARG005 |
|
| 535 | 540 |
result = runner.invoke(cli.derivepassphrase, ['--notes', 'sv'], |
| 536 | 541 |
catch_exceptions=False) |
| 537 | 542 |
assert (result.exit_code, result.stderr_bytes) == (0, b''), ( |
| 538 | 543 |
'program exited with failure' |
| 539 | 544 |
) |
| 540 |
- with open(cli._config_filename(), 'rt') as infile: |
|
| 545 |
+ with open(cli._config_filename(), encoding='UTF-8') as infile: |
|
| 541 | 546 |
config = json.load(infile) |
| 542 | 547 |
assert config == {'global': {'phrase': 'abc'},
|
| 543 | 548 |
'services': {'sv': {'notes':
|
| ... | ... |
@@ -548,13 +553,14 @@ contents go here |
| 548 | 553 |
with tests.isolated_config(monkeypatch=monkeypatch, runner=runner, |
| 549 | 554 |
config={'global': {'phrase': 'abc'},
|
| 550 | 555 |
'services': {}}):
|
| 551 |
- monkeypatch.setattr(click, 'edit', lambda *a, **kw: None) |
|
| 556 |
+ monkeypatch.setattr(click, 'edit', |
|
| 557 |
+ lambda *a, **kw: None) # noqa: ARG005 |
|
| 552 | 558 |
result = runner.invoke(cli.derivepassphrase, ['--notes', 'sv'], |
| 553 | 559 |
catch_exceptions=False) |
| 554 | 560 |
assert (result.exit_code, result.stderr_bytes) == (0, b''), ( |
| 555 | 561 |
'program exited with failure' |
| 556 | 562 |
) |
| 557 |
- with open(cli._config_filename(), 'rt') as infile: |
|
| 563 |
+ with open(cli._config_filename(), encoding='UTF-8') as infile: |
|
| 558 | 564 |
config = json.load(infile) |
| 559 | 565 |
assert config == {'global': {'phrase': 'abc'}, 'services': {}}
|
| 560 | 566 |
|
| ... | ... |
@@ -563,13 +569,14 @@ contents go here |
| 563 | 569 |
with tests.isolated_config(monkeypatch=monkeypatch, runner=runner, |
| 564 | 570 |
config={'global': {'phrase': 'abc'},
|
| 565 | 571 |
'services': {}}):
|
| 566 |
- monkeypatch.setattr(click, 'edit', lambda *a, **kw: 'long\ntext') |
|
| 572 |
+ monkeypatch.setattr(click, 'edit', |
|
| 573 |
+ lambda *a, **kw: 'long\ntext') # noqa: ARG005 |
|
| 567 | 574 |
result = runner.invoke(cli.derivepassphrase, ['--notes', 'sv'], |
| 568 | 575 |
catch_exceptions=False) |
| 569 | 576 |
assert (result.exit_code, result.stderr_bytes) == (0, b''), ( |
| 570 | 577 |
'program exited with failure' |
| 571 | 578 |
) |
| 572 |
- with open(cli._config_filename(), 'rt') as infile: |
|
| 579 |
+ with open(cli._config_filename(), encoding='UTF-8') as infile: |
|
| 573 | 580 |
config = json.load(infile) |
| 574 | 581 |
assert config == {'global': {'phrase': 'abc'},
|
| 575 | 582 |
'services': {'sv': {'notes': 'long\ntext'}}}
|
| ... | ... |
@@ -579,7 +586,8 @@ contents go here |
| 579 | 586 |
with tests.isolated_config(monkeypatch=monkeypatch, runner=runner, |
| 580 | 587 |
config={'global': {'phrase': 'abc'},
|
| 581 | 588 |
'services': {}}):
|
| 582 |
- monkeypatch.setattr(click, 'edit', lambda *a, **kw: '\n\n') |
|
| 589 |
+ monkeypatch.setattr(click, 'edit', |
|
| 590 |
+ lambda *a, **kw: '\n\n') # noqa: ARG005 |
|
| 583 | 591 |
result = runner.invoke(cli.derivepassphrase, ['--notes', 'sv'], |
| 584 | 592 |
catch_exceptions=False) |
| 585 | 593 |
assert result.exit_code != 0, 'program unexpectedly succeeded' |
| ... | ... |
@@ -587,7 +595,7 @@ contents go here |
| 587 | 595 |
assert b'user aborted request' in result.stderr_bytes, ( |
| 588 | 596 |
'expected error message missing' |
| 589 | 597 |
) |
| 590 |
- with open(cli._config_filename(), 'rt') as infile: |
|
| 598 |
+ with open(cli._config_filename(), encoding='UTF-8') as infile: |
|
| 591 | 599 |
config = json.load(infile) |
| 592 | 600 |
assert config == {'global': {'phrase': 'abc'}, 'services': {}}
|
| 593 | 601 |
|
| ... | ... |
@@ -632,10 +640,10 @@ contents go here |
| 632 | 640 |
monkeypatch.setattr(cli, '_get_suitable_ssh_keys', |
| 633 | 641 |
tests.suitable_ssh_keys) |
| 634 | 642 |
result = runner.invoke(cli.derivepassphrase, |
| 635 |
- ['--config'] + command_line, |
|
| 643 |
+ ['--config', *command_line], |
|
| 636 | 644 |
catch_exceptions=False, input=input) |
| 637 | 645 |
assert result.exit_code == 0, 'program exited with failure' |
| 638 |
- with open(cli._config_filename(), 'rt') as infile: |
|
| 646 |
+ with open(cli._config_filename(), encoding='UTF-8') as infile: |
|
| 639 | 647 |
config = json.load(infile) |
| 640 | 648 |
assert config == result_config, ( |
| 641 | 649 |
'stored config does not match expectation' |
| ... | ... |
@@ -662,7 +670,7 @@ contents go here |
| 662 | 670 |
monkeypatch.setattr(cli, '_get_suitable_ssh_keys', |
| 663 | 671 |
tests.suitable_ssh_keys) |
| 664 | 672 |
result = runner.invoke(cli.derivepassphrase, |
| 665 |
- ['--config'] + command_line, |
|
| 673 |
+ ['--config', *command_line], |
|
| 666 | 674 |
catch_exceptions=False, input=input) |
| 667 | 675 |
assert result.exit_code != 0, 'program unexpectedly succeeded?!' |
| 668 | 676 |
assert result.stderr_bytes is not None |
| ... | ... |
@@ -677,15 +685,16 @@ contents go here |
| 677 | 685 |
with tests.isolated_config(monkeypatch=monkeypatch, runner=runner, |
| 678 | 686 |
config={'global': {'phrase': 'abc'},
|
| 679 | 687 |
'services': {}}):
|
| 688 |
+ custom_error = 'custom error message' |
|
| 680 | 689 |
def raiser(): |
| 681 |
- raise RuntimeError('custom error message')
|
|
| 690 |
+ raise RuntimeError(custom_error) |
|
| 682 | 691 |
monkeypatch.setattr(cli, '_select_ssh_key', raiser) |
| 683 | 692 |
result = runner.invoke(cli.derivepassphrase, |
| 684 | 693 |
['--key', '--config'], |
| 685 | 694 |
catch_exceptions=False) |
| 686 | 695 |
assert result.exit_code != 0, 'program unexpectedly succeeded' |
| 687 | 696 |
assert result.stderr_bytes is not None |
| 688 |
- assert b'custom error message' in result.stderr_bytes, ( |
|
| 697 |
+ assert custom_error.encode() in result.stderr_bytes, ( |
|
| 689 | 698 |
'expected error message missing' |
| 690 | 699 |
) |
| 691 | 700 |
|
| ... | ... |
@@ -714,13 +723,15 @@ class TestCLIUtils: |
| 714 | 723 |
|
| 715 | 724 |
def test_100_save_bad_config(self, monkeypatch: Any) -> None: |
| 716 | 725 |
runner = click.testing.CliRunner() |
| 717 |
- with tests.isolated_config(monkeypatch=monkeypatch, runner=runner, |
|
| 718 |
- config={}):
|
|
| 719 |
- with pytest.raises(ValueError, match='Invalid vault config'): |
|
| 726 |
+ with ( |
|
| 727 |
+ tests.isolated_config(monkeypatch=monkeypatch, runner=runner, |
|
| 728 |
+ config={}),
|
|
| 729 |
+ pytest.raises(ValueError, match='Invalid vault config') |
|
| 730 |
+ ): |
|
| 720 | 731 |
cli._save_config(None) # type: ignore |
| 721 | 732 |
|
| 722 | 733 |
|
| 723 |
- def test_101_prompt_for_selection_multiple(self, monkeypatch: Any) -> None: |
|
| 734 |
+ def test_101_prompt_for_selection_multiple(self) -> None: |
|
| 724 | 735 |
@click.command() |
| 725 | 736 |
@click.option('--heading', default='Our menu:')
|
| 726 | 737 |
@click.argument('items', nargs=-1)
|
| ... | ... |
@@ -786,7 +797,7 @@ Your selection? (1-10, leave empty to abort): \n''', ( # noqa: E501 |
| 786 | 797 |
) |
| 787 | 798 |
|
| 788 | 799 |
|
| 789 |
- def test_102_prompt_for_selection_single(self, monkeypatch: Any) -> None: |
|
| 800 |
+ def test_102_prompt_for_selection_single(self) -> None: |
|
| 790 | 801 |
@click.command() |
| 791 | 802 |
@click.option('--item', default='baked beans')
|
| 792 | 803 |
@click.argument('prompt')
|
| ... | ... |
@@ -794,9 +805,9 @@ Your selection? (1-10, leave empty to abort): \n''', ( # noqa: E501 |
| 794 | 805 |
try: |
| 795 | 806 |
cli._prompt_for_selection([item], heading='', |
| 796 | 807 |
single_choice_prompt=prompt) |
| 797 |
- except IndexError as e: |
|
| 808 |
+ except IndexError: |
|
| 798 | 809 |
click.echo('Boo.')
|
| 799 |
- raise e |
|
| 810 |
+ raise |
|
| 800 | 811 |
else: |
| 801 | 812 |
click.echo('Great!')
|
| 802 | 813 |
runner = click.testing.CliRunner(mix_stderr=True) |
| ... | ... |
@@ -810,13 +821,13 @@ Will replace with spam. Confirm, y/n? y |
| 810 | 821 |
Great! |
| 811 | 822 |
''', 'driver program produced unexpected output' |
| 812 | 823 |
result = runner.invoke(driver, |
| 813 |
- ['Will replace with spam, okay? ' + |
|
| 824 |
+ ['Will replace with spam, okay? ' |
|
| 814 | 825 |
'(Please say "y" or "n".)'], |
| 815 | 826 |
input='') |
| 816 | 827 |
assert result.exit_code > 0, 'driver program succeeded?!' |
| 817 | 828 |
assert result.stdout == '''\ |
| 818 | 829 |
[1] baked beans |
| 819 |
-Will replace with spam, okay? (Please say "y" or "n".): |
|
| 830 |
+Will replace with spam, okay? (Please say "y" or "n".):\x20 |
|
| 820 | 831 |
Boo. |
| 821 | 832 |
''', 'driver program produced unexpected output' |
| 822 | 833 |
assert isinstance(result.exception, IndexError), ( |
| ... | ... |
@@ -829,19 +840,14 @@ Boo. |
| 829 | 840 |
lambda *a, **kw: |
| 830 | 841 |
json.dumps({'args': a, 'kwargs': kw}))
|
| 831 | 842 |
res = json.loads(cli._prompt_for_passphrase()) |
| 832 |
- assert 'args' in res and 'kwargs' in res, ( |
|
| 833 |
- 'missing arguments to passphrase prompt' |
|
| 834 |
- ) |
|
| 835 |
- assert res['args'][:1] == ['Passphrase'], ( |
|
| 836 |
- 'missing arguments to passphrase prompt' |
|
| 837 |
- ) |
|
| 838 |
- assert (res['kwargs'].get('default') == ''
|
|
| 839 |
- and not res['kwargs'].get('show_default', True)), (
|
|
| 840 |
- 'missing arguments to passphrase prompt' |
|
| 841 |
- ) |
|
| 842 |
- assert res['kwargs'].get('err') and res['kwargs'].get('hide_input'), (
|
|
| 843 |
- 'missing arguments to passphrase prompt' |
|
| 844 |
- ) |
|
| 843 |
+ err_msg = 'missing arguments to passphrase prompt' |
|
| 844 |
+ assert 'args' in res, err_msg |
|
| 845 |
+ assert 'kwargs' in res, err_msg |
|
| 846 |
+ assert res['args'][:1] == ['Passphrase'], err_msg |
|
| 847 |
+ assert res['kwargs'].get('default') == '', err_msg
|
|
| 848 |
+ assert not res['kwargs'].get('show_default', True), err_msg
|
|
| 849 |
+ assert res['kwargs'].get('err'), err_msg
|
|
| 850 |
+ assert res['kwargs'].get('hide_input'), err_msg
|
|
| 845 | 851 |
|
| 846 | 852 |
|
| 847 | 853 |
@pytest.mark.parametrize(['command_line', 'config', 'result_config'], [ |
| ... | ... |
@@ -869,7 +875,7 @@ Boo. |
| 869 | 875 |
assert (result.exit_code, result.stderr_bytes) == (0, b''), ( |
| 870 | 876 |
'program exited with failure' |
| 871 | 877 |
) |
| 872 |
- with open(cli._config_filename(), 'rt') as infile: |
|
| 878 |
+ with open(cli._config_filename(), encoding='UTF-8') as infile: |
|
| 873 | 879 |
config_readback = json.load(infile) |
| 874 | 880 |
assert config_readback == result_config |
| 875 | 881 |
|
| ... | ... |
@@ -890,14 +896,13 @@ Boo. |
| 890 | 896 |
vfunc: Callable[[click.Context, click.Parameter, Any], int | None], |
| 891 | 897 |
input: int, |
| 892 | 898 |
) -> None: |
| 893 |
- ctx = cli.derivepassphrase.make_context(cli.prog_name, []) |
|
| 899 |
+ ctx = cli.derivepassphrase.make_context(cli.PROG_NAME, []) |
|
| 894 | 900 |
param = cli.derivepassphrase.params[0] |
| 895 | 901 |
assert vfunc(ctx, param, input) == input |
| 896 | 902 |
|
| 897 | 903 |
|
| 898 | 904 |
@tests.skip_if_no_agent |
| 899 |
- @pytest.mark.parametrize(['conn_hint'], |
|
| 900 |
- [('none',), ('socket',), ('client',)])
|
|
| 905 |
+ @pytest.mark.parametrize('conn_hint', ['none', 'socket', 'client'])
|
|
| 901 | 906 |
def test_227_get_suitable_ssh_keys( |
| 902 | 907 |
self, monkeypatch: Any, conn_hint: str, |
| 903 | 908 |
) -> None: |
| ... | ... |
@@ -918,7 +923,7 @@ Boo. |
| 918 | 923 |
list(cli._get_suitable_ssh_keys(hint)) |
| 919 | 924 |
except RuntimeError: # pragma: no cover |
| 920 | 925 |
pass |
| 921 |
- except Exception as e: # pragma: no cover |
|
| 926 |
+ except Exception as e: # noqa: BLE001 # pragma: no cover |
|
| 922 | 927 |
exception = e |
| 923 | 928 |
finally: |
| 924 | 929 |
assert exception is None, 'exception querying suitable SSH keys' |
| ... | ... |
@@ -90,14 +92,14 @@ class TestSequin: |
| 90 | 92 |
seq = sequin.Sequin([1, 0, 1, 0, 0, 1, 0, 0, 0, 1], is_bitstring=True) |
| 91 | 93 |
assert seq.bases == {2: collections.deque([
|
| 92 | 94 |
1, 0, 1, 0, 0, 1, 0, 0, 0, 1])} |
| 93 |
- # |
|
| 95 |
+ |
|
| 94 | 96 |
assert seq._all_or_nothing_shift(3) == (1, 0, 1) |
| 95 | 97 |
assert seq._all_or_nothing_shift(3) == (0, 0, 1) |
| 96 | 98 |
assert seq.bases[2] == collections.deque([0, 0, 0, 1]) |
| 97 |
- # |
|
| 99 |
+ |
|
| 98 | 100 |
assert seq._all_or_nothing_shift(5) == () |
| 99 | 101 |
assert seq.bases[2] == collections.deque([0, 0, 0, 1]) |
| 100 |
- # |
|
| 102 |
+ |
|
| 101 | 103 |
assert seq._all_or_nothing_shift(4), (0, 0, 0, 1) |
| 102 | 104 |
assert 2 not in seq.bases |
| 103 | 105 |
|
| ... | ... |
@@ -106,7 +108,7 @@ class TestSequin: |
| 106 | 108 |
[ |
| 107 | 109 |
([0, 1, 2, 3, 4, 5, 6, 7], True, |
| 108 | 110 |
ValueError, 'sequence item out of range'), |
| 109 |
- (u'こんにちは。', False, |
|
| 111 |
+ ('こんにちは。', False,
|
|
| 110 | 112 |
ValueError, 'sequence item out of range'), |
| 111 | 113 |
] |
| 112 | 114 |
) |
| ... | ... |
@@ -11,13 +11,14 @@ import io |
| 11 | 11 |
import os |
| 12 | 12 |
import socket |
| 13 | 13 |
import subprocess |
| 14 |
-from typing_extensions import Any |
|
| 15 | 14 |
|
| 16 | 15 |
import click |
| 17 | 16 |
import click.testing |
| 17 |
+import pytest |
|
| 18 |
+from typing_extensions import Any |
|
| 19 |
+ |
|
| 18 | 20 |
import derivepassphrase |
| 19 | 21 |
import derivepassphrase.cli |
| 20 |
-import pytest |
|
| 21 | 22 |
import ssh_agent_client |
| 22 | 23 |
import tests |
| 23 | 24 |
|
| ... | ... |
@@ -122,6 +124,7 @@ class TestAgentInteraction: |
| 122 | 124 |
@pytest.mark.parametrize(['keytype', 'data_dict'], |
| 123 | 125 |
list(tests.SUPPORTED_KEYS.items())) |
| 124 | 126 |
def test_200_sign_data_via_agent(self, keytype, data_dict): |
| 127 |
+ del keytype # Unused. |
|
| 125 | 128 |
private_key = data_dict['private_key'] |
| 126 | 129 |
try: |
| 127 | 130 |
_ = subprocess.run(['ssh-add', '-t', '30', '-q', '-'], |
| ... | ... |
@@ -159,6 +162,7 @@ class TestAgentInteraction: |
| 159 | 162 |
@pytest.mark.parametrize(['keytype', 'data_dict'], |
| 160 | 163 |
list(tests.UNSUITABLE_KEYS.items())) |
| 161 | 164 |
def test_201_sign_data_via_agent_unsupported(self, keytype, data_dict): |
| 165 |
+ del keytype # Unused. |
|
| 162 | 166 |
private_key = data_dict['private_key'] |
| 163 | 167 |
try: |
| 164 | 168 |
_ = subprocess.run(['ssh-add', '-t', '30', '-q', '-'], |
| ... | ... |
@@ -246,18 +250,18 @@ class TestAgentInteraction: |
| 246 | 250 |
monkeypatch.setenv('SSH_AUTH_SOCK',
|
| 247 | 251 |
os.environ['SSH_AUTH_SOCK'] + '~') |
| 248 | 252 |
sock = socket.socket(family=socket.AF_UNIX) |
| 249 |
- with pytest.raises(OSError): |
|
| 253 |
+ with pytest.raises(OSError): # noqa: PT011 |
|
| 250 | 254 |
ssh_agent_client.SSHAgentClient(socket=sock) |
| 251 | 255 |
|
| 252 |
- @pytest.mark.parametrize(['response'], [ |
|
| 253 |
- (b'\x00\x00',), |
|
| 254 |
- (b'\x00\x00\x00\x1f some bytes missing',), |
|
| 256 |
+ @pytest.mark.parametrize('response', [
|
|
| 257 |
+ b'\x00\x00', |
|
| 258 |
+ b'\x00\x00\x00\x1f some bytes missing', |
|
| 255 | 259 |
]) |
| 256 | 260 |
def test_310_truncated_server_response(self, monkeypatch, response): |
| 257 | 261 |
client = ssh_agent_client.SSHAgentClient() |
| 258 | 262 |
response_stream = io.BytesIO(response) |
| 259 |
- class PseudoSocket(object): |
|
| 260 |
- def sendall(self, *args: Any, **kwargs: Any) -> Any: |
|
| 263 |
+ class PseudoSocket: |
|
| 264 |
+ def sendall(self, *args: Any, **kwargs: Any) -> Any: # noqa: ARG002 |
|
| 261 | 265 |
return None |
| 262 | 266 |
def recv(self, *args: Any, **kwargs: Any) -> Any: |
| 263 | 267 |
return response_stream.read(*args, **kwargs) |
| ... | ... |
@@ -276,7 +280,7 @@ class TestAgentInteraction: |
| 276 | 280 |
12, |
| 277 | 281 |
b'\x00\x00\x00\x00abc', |
| 278 | 282 |
ssh_agent_client.TrailingDataError, |
| 279 |
- 'overlong response', |
|
| 283 |
+ 'Overlong response', |
|
| 280 | 284 |
), |
| 281 | 285 |
] |
| 282 | 286 |
) |
| ... | ... |
@@ -284,7 +288,7 @@ class TestAgentInteraction: |
| 284 | 288 |
response, exc_type, exc_pattern): |
| 285 | 289 |
client = ssh_agent_client.SSHAgentClient() |
| 286 | 290 |
monkeypatch.setattr(client, 'request', |
| 287 |
- lambda *a, **kw: (response_code, response)) |
|
| 291 |
+ lambda *a, **kw: (response_code, response)) # noqa: ARG005 |
|
| 288 | 292 |
with pytest.raises(exc_type, match=exc_pattern): |
| 289 | 293 |
client.list_keys() |
| 290 | 294 |
|
| ... | ... |
@@ -311,8 +315,8 @@ class TestAgentInteraction: |
| 311 | 315 |
def test_330_sign_error_responses(self, monkeypatch, key, check, |
| 312 | 316 |
response, exc_type, exc_pattern): |
| 313 | 317 |
client = ssh_agent_client.SSHAgentClient() |
| 314 |
- monkeypatch.setattr(client, 'request', lambda a, b: response) |
|
| 315 |
- KeyCommentPair = ssh_agent_client.types.KeyCommentPair |
|
| 318 |
+ monkeypatch.setattr(client, 'request', lambda a, b: response) # noqa: ARG005 |
|
| 319 |
+ KeyCommentPair = ssh_agent_client.types.KeyCommentPair # noqa: N806 |
|
| 316 | 320 |
loaded_keys = [KeyCommentPair(v['public_key_data'], b'no comment') |
| 317 | 321 |
for v in tests.SUPPORTED_KEYS.values()] |
| 318 | 322 |
monkeypatch.setattr(client, 'list_keys', lambda: loaded_keys) |
| 319 | 323 |