Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

import argparse 

import sys as _sys 

import warnings 

from gettext import gettext as _ 

 

import py 

import six 

 

from ..main import EXIT_USAGEERROR 

 

FILE_OR_DIR = "file_or_dir" 

 

 

class Parser(object): 

""" Parser for command line arguments and ini-file values. 

 

:ivar extra_info: dict of generic param -> value to display in case 

there's an error processing the command line arguments. 

""" 

 

prog = None 

 

def __init__(self, usage=None, processopt=None): 

self._anonymous = OptionGroup("custom options", parser=self) 

self._groups = [] 

self._processopt = processopt 

self._usage = usage 

self._inidict = {} 

self._ininames = [] 

self.extra_info = {} 

 

def processoption(self, option): 

if self._processopt: 

if option.dest: 

self._processopt(option) 

 

def getgroup(self, name, description="", after=None): 

""" get (or create) a named option Group. 

 

:name: name of the option group. 

:description: long description for --help output. 

:after: name of other group, used for ordering --help output. 

 

The returned group object has an ``addoption`` method with the same 

signature as :py:func:`parser.addoption 

<_pytest.config.Parser.addoption>` but will be shown in the 

respective group in the output of ``pytest. --help``. 

""" 

for group in self._groups: 

if group.name == name: 

return group 

group = OptionGroup(name, description, parser=self) 

i = 0 

for i, grp in enumerate(self._groups): 

if grp.name == after: 

break 

self._groups.insert(i + 1, group) 

return group 

 

def addoption(self, *opts, **attrs): 

""" register a command line option. 

 

:opts: option names, can be short or long options. 

:attrs: same attributes which the ``add_option()`` function of the 

`argparse library 

<http://docs.python.org/2/library/argparse.html>`_ 

accepts. 

 

After command line parsing options are available on the pytest config 

object via ``config.option.NAME`` where ``NAME`` is usually set 

by passing a ``dest`` attribute, for example 

``addoption("--long", dest="NAME", ...)``. 

""" 

self._anonymous.addoption(*opts, **attrs) 

 

def parse(self, args, namespace=None): 

from _pytest._argcomplete import try_argcomplete 

 

self.optparser = self._getparser() 

try_argcomplete(self.optparser) 

args = [str(x) if isinstance(x, py.path.local) else x for x in args] 

return self.optparser.parse_args(args, namespace=namespace) 

 

def _getparser(self): 

from _pytest._argcomplete import filescompleter 

 

optparser = MyOptionParser(self, self.extra_info, prog=self.prog) 

groups = self._groups + [self._anonymous] 

for group in groups: 

if group.options: 

desc = group.description or group.name 

arggroup = optparser.add_argument_group(desc) 

for option in group.options: 

n = option.names() 

a = option.attrs() 

arggroup.add_argument(*n, **a) 

# bash like autocompletion for dirs (appending '/') 

optparser.add_argument(FILE_OR_DIR, nargs="*").completer = filescompleter 

return optparser 

 

def parse_setoption(self, args, option, namespace=None): 

parsedoption = self.parse(args, namespace=namespace) 

for name, value in parsedoption.__dict__.items(): 

setattr(option, name, value) 

return getattr(parsedoption, FILE_OR_DIR) 

 

def parse_known_args(self, args, namespace=None): 

"""parses and returns a namespace object with known arguments at this 

point. 

""" 

return self.parse_known_and_unknown_args(args, namespace=namespace)[0] 

 

def parse_known_and_unknown_args(self, args, namespace=None): 

"""parses and returns a namespace object with known arguments, and 

the remaining arguments unknown at this point. 

""" 

optparser = self._getparser() 

args = [str(x) if isinstance(x, py.path.local) else x for x in args] 

return optparser.parse_known_args(args, namespace=namespace) 

 

def addini(self, name, help, type=None, default=None): 

""" register an ini-file option. 

 

:name: name of the ini-variable 

:type: type of the variable, can be ``pathlist``, ``args``, ``linelist`` 

or ``bool``. 

:default: default value if no ini-file option exists but is queried. 

 

The value of ini-variables can be retrieved via a call to 

:py:func:`config.getini(name) <_pytest.config.Config.getini>`. 

""" 

assert type in (None, "pathlist", "args", "linelist", "bool") 

self._inidict[name] = (help, type, default) 

self._ininames.append(name) 

 

 

class ArgumentError(Exception): 

""" 

Raised if an Argument instance is created with invalid or 

inconsistent arguments. 

""" 

 

def __init__(self, msg, option): 

self.msg = msg 

self.option_id = str(option) 

 

def __str__(self): 

if self.option_id: 

return "option %s: %s" % (self.option_id, self.msg) 

else: 

return self.msg 

 

 

class Argument(object): 

"""class that mimics the necessary behaviour of optparse.Option 

 

it's currently a least effort implementation 

and ignoring choices and integer prefixes 

https://docs.python.org/3/library/optparse.html#optparse-standard-option-types 

""" 

 

_typ_map = {"int": int, "string": str, "float": float, "complex": complex} 

 

def __init__(self, *names, **attrs): 

"""store parms in private vars for use in add_argument""" 

self._attrs = attrs 

self._short_opts = [] 

self._long_opts = [] 

self.dest = attrs.get("dest") 

if "%default" in (attrs.get("help") or ""): 

warnings.warn( 

'pytest now uses argparse. "%default" should be' 

' changed to "%(default)s" ', 

DeprecationWarning, 

stacklevel=3, 

) 

try: 

typ = attrs["type"] 

except KeyError: 

pass 

else: 

# this might raise a keyerror as well, don't want to catch that 

if isinstance(typ, six.string_types): 

if typ == "choice": 

warnings.warn( 

"`type` argument to addoption() is the string %r." 

" For choices this is optional and can be omitted, " 

" but when supplied should be a type (for example `str` or `int`)." 

" (options: %s)" % (typ, names), 

DeprecationWarning, 

stacklevel=4, 

) 

# argparse expects a type here take it from 

# the type of the first element 

attrs["type"] = type(attrs["choices"][0]) 

else: 

warnings.warn( 

"`type` argument to addoption() is the string %r, " 

" but when supplied should be a type (for example `str` or `int`)." 

" (options: %s)" % (typ, names), 

DeprecationWarning, 

stacklevel=4, 

) 

attrs["type"] = Argument._typ_map[typ] 

# used in test_parseopt -> test_parse_defaultgetter 

self.type = attrs["type"] 

else: 

self.type = typ 

try: 

# attribute existence is tested in Config._processopt 

self.default = attrs["default"] 

except KeyError: 

pass 

self._set_opt_strings(names) 

if not self.dest: 

if self._long_opts: 

self.dest = self._long_opts[0][2:].replace("-", "_") 

else: 

try: 

self.dest = self._short_opts[0][1:] 

except IndexError: 

raise ArgumentError("need a long or short option", self) 

 

def names(self): 

return self._short_opts + self._long_opts 

 

def attrs(self): 

# update any attributes set by processopt 

attrs = "default dest help".split() 

if self.dest: 

attrs.append(self.dest) 

for attr in attrs: 

try: 

self._attrs[attr] = getattr(self, attr) 

except AttributeError: 

pass 

if self._attrs.get("help"): 

a = self._attrs["help"] 

a = a.replace("%default", "%(default)s") 

# a = a.replace('%prog', '%(prog)s') 

self._attrs["help"] = a 

return self._attrs 

 

def _set_opt_strings(self, opts): 

"""directly from optparse 

 

might not be necessary as this is passed to argparse later on""" 

for opt in opts: 

if len(opt) < 2: 

raise ArgumentError( 

"invalid option string %r: " 

"must be at least two characters long" % opt, 

self, 

) 

elif len(opt) == 2: 

if not (opt[0] == "-" and opt[1] != "-"): 

raise ArgumentError( 

"invalid short option string %r: " 

"must be of the form -x, (x any non-dash char)" % opt, 

self, 

) 

self._short_opts.append(opt) 

else: 

if not (opt[0:2] == "--" and opt[2] != "-"): 

raise ArgumentError( 

"invalid long option string %r: " 

"must start with --, followed by non-dash" % opt, 

self, 

) 

self._long_opts.append(opt) 

 

def __repr__(self): 

args = [] 

if self._short_opts: 

args += ["_short_opts: " + repr(self._short_opts)] 

if self._long_opts: 

args += ["_long_opts: " + repr(self._long_opts)] 

args += ["dest: " + repr(self.dest)] 

if hasattr(self, "type"): 

args += ["type: " + repr(self.type)] 

if hasattr(self, "default"): 

args += ["default: " + repr(self.default)] 

return "Argument({})".format(", ".join(args)) 

 

 

class OptionGroup(object): 

def __init__(self, name, description="", parser=None): 

self.name = name 

self.description = description 

self.options = [] 

self.parser = parser 

 

def addoption(self, *optnames, **attrs): 

""" add an option to this group. 

 

if a shortened version of a long option is specified it will 

be suppressed in the help. addoption('--twowords', '--two-words') 

results in help showing '--two-words' only, but --twowords gets 

accepted **and** the automatic destination is in args.twowords 

""" 

conflict = set(optnames).intersection( 

name for opt in self.options for name in opt.names() 

) 

if conflict: 

raise ValueError("option names %s already added" % conflict) 

option = Argument(*optnames, **attrs) 

self._addoption_instance(option, shortupper=False) 

 

def _addoption(self, *optnames, **attrs): 

option = Argument(*optnames, **attrs) 

self._addoption_instance(option, shortupper=True) 

 

def _addoption_instance(self, option, shortupper=False): 

if not shortupper: 

for opt in option._short_opts: 

if opt[0] == "-" and opt[1].islower(): 

raise ValueError("lowercase shortoptions reserved") 

if self.parser: 

self.parser.processoption(option) 

self.options.append(option) 

 

 

class MyOptionParser(argparse.ArgumentParser): 

def __init__(self, parser, extra_info=None, prog=None): 

if not extra_info: 

extra_info = {} 

self._parser = parser 

argparse.ArgumentParser.__init__( 

self, 

prog=prog, 

usage=parser._usage, 

add_help=False, 

formatter_class=DropShorterLongHelpFormatter, 

) 

# extra_info is a dict of (param -> value) to display if there's 

# an usage error to provide more contextual information to the user 

self.extra_info = extra_info 

 

def error(self, message): 

"""error(message: string) 

 

Prints a usage message incorporating the message to stderr and 

exits. 

Overrides the method in parent class to change exit code""" 

self.print_usage(_sys.stderr) 

args = {"prog": self.prog, "message": message} 

self.exit(EXIT_USAGEERROR, _("%(prog)s: error: %(message)s\n") % args) 

 

def parse_args(self, args=None, namespace=None): 

"""allow splitting of positional arguments""" 

args, argv = self.parse_known_args(args, namespace) 

if argv: 

for arg in argv: 

if arg and arg[0] == "-": 

lines = ["unrecognized arguments: %s" % (" ".join(argv))] 

for k, v in sorted(self.extra_info.items()): 

lines.append(" %s: %s" % (k, v)) 

self.error("\n".join(lines)) 

getattr(args, FILE_OR_DIR).extend(argv) 

return args 

 

 

class DropShorterLongHelpFormatter(argparse.HelpFormatter): 

"""shorten help for long options that differ only in extra hyphens 

 

- collapse **long** options that are the same except for extra hyphens 

- special action attribute map_long_option allows surpressing additional 

long options 

- shortcut if there are only two options and one of them is a short one 

- cache result on action object as this is called at least 2 times 

""" 

 

def _format_action_invocation(self, action): 

orgstr = argparse.HelpFormatter._format_action_invocation(self, action) 

if orgstr and orgstr[0] != "-": # only optional arguments 

return orgstr 

res = getattr(action, "_formatted_action_invocation", None) 

if res: 

return res 

options = orgstr.split(", ") 

if len(options) == 2 and (len(options[0]) == 2 or len(options[1]) == 2): 

# a shortcut for '-h, --help' or '--abc', '-a' 

action._formatted_action_invocation = orgstr 

return orgstr 

return_list = [] 

option_map = getattr(action, "map_long_option", {}) 

if option_map is None: 

option_map = {} 

short_long = {} 

for option in options: 

if len(option) == 2 or option[2] == " ": 

continue 

if not option.startswith("--"): 

raise ArgumentError( 

'long optional argument without "--": [%s]' % (option), self 

) 

xxoption = option[2:] 

if xxoption.split()[0] not in option_map: 

shortened = xxoption.replace("-", "") 

if shortened not in short_long or len(short_long[shortened]) < len( 

xxoption 

): 

short_long[shortened] = xxoption 

# now short_long has been filled out to the longest with dashes 

# **and** we keep the right option ordering from add_argument 

for option in options: 

if len(option) == 2 or option[2] == " ": 

return_list.append(option) 

if option[2:] == short_long.get(option.replace("-", "")): 

return_list.append(option.replace(" ", "=", 1)) 

action._formatted_action_invocation = ", ".join(return_list) 

return action._formatted_action_invocation