Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(220)

Side by Side Diff: third_party/google-endpoints/setuptools/config.py

Issue 2666783008: Add google-endpoints to third_party/. (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 from __future__ import absolute_import, unicode_literals
2 import io
3 import os
4 import sys
5 from collections import defaultdict
6 from functools import partial
7
8 from distutils.errors import DistutilsOptionError, DistutilsFileError
9 from setuptools.py26compat import import_module
10 from six import string_types
11
12
13 def read_configuration(
14 filepath, find_others=False, ignore_option_errors=False):
15 """Read given configuration file and returns options from it as a dict.
16
17 :param str|unicode filepath: Path to configuration file
18 to get options from.
19
20 :param bool find_others: Whether to search for other configuration files
21 which could be on in various places.
22
23 :param bool ignore_option_errors: Whether to silently ignore
24 options, values of which could not be resolved (e.g. due to exceptions
25 in directives such as file:, attr:, etc.).
26 If False exceptions are propagated as expected.
27
28 :rtype: dict
29 """
30 from setuptools.dist import Distribution, _Distribution
31
32 filepath = os.path.abspath(filepath)
33
34 if not os.path.isfile(filepath):
35 raise DistutilsFileError(
36 'Configuration file %s does not exist.' % filepath)
37
38 current_directory = os.getcwd()
39 os.chdir(os.path.dirname(filepath))
40
41 try:
42 dist = Distribution()
43
44 filenames = dist.find_config_files() if find_others else []
45 if filepath not in filenames:
46 filenames.append(filepath)
47
48 _Distribution.parse_config_files(dist, filenames=filenames)
49
50 handlers = parse_configuration(
51 dist, dist.command_options,
52 ignore_option_errors=ignore_option_errors)
53
54 finally:
55 os.chdir(current_directory)
56
57 return configuration_to_dict(handlers)
58
59
60 def configuration_to_dict(handlers):
61 """Returns configuration data gathered by given handlers as a dict.
62
63 :param list[ConfigHandler] handlers: Handlers list,
64 usually from parse_configuration()
65
66 :rtype: dict
67 """
68 config_dict = defaultdict(dict)
69
70 for handler in handlers:
71
72 obj_alias = handler.section_prefix
73 target_obj = handler.target_obj
74
75 for option in handler.set_options:
76 getter = getattr(target_obj, 'get_%s' % option, None)
77
78 if getter is None:
79 value = getattr(target_obj, option)
80
81 else:
82 value = getter()
83
84 config_dict[obj_alias][option] = value
85
86 return config_dict
87
88
89 def parse_configuration(
90 distribution, command_options, ignore_option_errors=False):
91 """Performs additional parsing of configuration options
92 for a distribution.
93
94 Returns a list of used option handlers.
95
96 :param Distribution distribution:
97 :param dict command_options:
98 :param bool ignore_option_errors: Whether to silently ignore
99 options, values of which could not be resolved (e.g. due to exceptions
100 in directives such as file:, attr:, etc.).
101 If False exceptions are propagated as expected.
102 :rtype: list
103 """
104 meta = ConfigMetadataHandler(
105 distribution.metadata, command_options, ignore_option_errors)
106 meta.parse()
107
108 options = ConfigOptionsHandler(
109 distribution, command_options, ignore_option_errors)
110 options.parse()
111
112 return [meta, options]
113
114
115 class ConfigHandler(object):
116 """Handles metadata supplied in configuration files."""
117
118 section_prefix = None
119 """Prefix for config sections handled by this handler.
120 Must be provided by class heirs.
121
122 """
123
124 aliases = {}
125 """Options aliases.
126 For compatibility with various packages. E.g.: d2to1 and pbr.
127 Note: `-` in keys is replaced with `_` by config parser.
128
129 """
130
131 def __init__(self, target_obj, options, ignore_option_errors=False):
132 sections = {}
133
134 section_prefix = self.section_prefix
135 for section_name, section_options in options.items():
136 if not section_name.startswith(section_prefix):
137 continue
138
139 section_name = section_name.replace(section_prefix, '').strip('.')
140 sections[section_name] = section_options
141
142 self.ignore_option_errors = ignore_option_errors
143 self.target_obj = target_obj
144 self.sections = sections
145 self.set_options = []
146
147 @property
148 def parsers(self):
149 """Metadata item name to parser function mapping."""
150 raise NotImplementedError(
151 '%s must provide .parsers property' % self.__class__.__name__)
152
153 def __setitem__(self, option_name, value):
154 unknown = tuple()
155 target_obj = self.target_obj
156
157 # Translate alias into real name.
158 option_name = self.aliases.get(option_name, option_name)
159
160 current_value = getattr(target_obj, option_name, unknown)
161
162 if current_value is unknown:
163 raise KeyError(option_name)
164
165 if current_value:
166 # Already inhabited. Skipping.
167 return
168
169 skip_option = False
170 parser = self.parsers.get(option_name)
171 if parser:
172 try:
173 value = parser(value)
174
175 except Exception:
176 skip_option = True
177 if not self.ignore_option_errors:
178 raise
179
180 if skip_option:
181 return
182
183 setter = getattr(target_obj, 'set_%s' % option_name, None)
184 if setter is None:
185 setattr(target_obj, option_name, value)
186 else:
187 setter(value)
188
189 self.set_options.append(option_name)
190
191 @classmethod
192 def _parse_list(cls, value, separator=','):
193 """Represents value as a list.
194
195 Value is split either by separator (defaults to comma) or by lines.
196
197 :param value:
198 :param separator: List items separator character.
199 :rtype: list
200 """
201 if isinstance(value, list): # _get_parser_compound case
202 return value
203
204 if '\n' in value:
205 value = value.splitlines()
206 else:
207 value = value.split(separator)
208
209 return [chunk.strip() for chunk in value if chunk.strip()]
210
211 @classmethod
212 def _parse_dict(cls, value):
213 """Represents value as a dict.
214
215 :param value:
216 :rtype: dict
217 """
218 separator = '='
219 result = {}
220 for line in cls._parse_list(value):
221 key, sep, val = line.partition(separator)
222 if sep != separator:
223 raise DistutilsOptionError(
224 'Unable to parse option value to dict: %s' % value)
225 result[key.strip()] = val.strip()
226
227 return result
228
229 @classmethod
230 def _parse_bool(cls, value):
231 """Represents value as boolean.
232
233 :param value:
234 :rtype: bool
235 """
236 value = value.lower()
237 return value in ('1', 'true', 'yes')
238
239 @classmethod
240 def _parse_file(cls, value):
241 """Represents value as a string, allowing including text
242 from nearest files using `file:` directive.
243
244 Directive is sandboxed and won't reach anything outside
245 directory with setup.py.
246
247 Examples:
248 include: LICENSE
249 include: src/file.txt
250
251 :param str value:
252 :rtype: str
253 """
254 if not isinstance(value, string_types):
255 return value
256
257 include_directive = 'file:'
258 if not value.startswith(include_directive):
259 return value
260
261 current_directory = os.getcwd()
262
263 filepath = value.replace(include_directive, '').strip()
264 filepath = os.path.abspath(filepath)
265
266 if not filepath.startswith(current_directory):
267 raise DistutilsOptionError(
268 '`file:` directive can not access %s' % filepath)
269
270 if os.path.isfile(filepath):
271 with io.open(filepath, encoding='utf-8') as f:
272 value = f.read()
273
274 return value
275
276 @classmethod
277 def _parse_attr(cls, value):
278 """Represents value as a module attribute.
279
280 Examples:
281 attr: package.attr
282 attr: package.module.attr
283
284 :param str value:
285 :rtype: str
286 """
287 attr_directive = 'attr:'
288 if not value.startswith(attr_directive):
289 return value
290
291 attrs_path = value.replace(attr_directive, '').strip().split('.')
292 attr_name = attrs_path.pop()
293
294 module_name = '.'.join(attrs_path)
295 module_name = module_name or '__init__'
296
297 sys.path.insert(0, os.getcwd())
298 try:
299 module = import_module(module_name)
300 value = getattr(module, attr_name)
301
302 finally:
303 sys.path = sys.path[1:]
304
305 return value
306
307 @classmethod
308 def _get_parser_compound(cls, *parse_methods):
309 """Returns parser function to represents value as a list.
310
311 Parses a value applying given methods one after another.
312
313 :param parse_methods:
314 :rtype: callable
315 """
316 def parse(value):
317 parsed = value
318
319 for method in parse_methods:
320 parsed = method(parsed)
321
322 return parsed
323
324 return parse
325
326 @classmethod
327 def _parse_section_to_dict(cls, section_options, values_parser=None):
328 """Parses section options into a dictionary.
329
330 Optionally applies a given parser to values.
331
332 :param dict section_options:
333 :param callable values_parser:
334 :rtype: dict
335 """
336 value = {}
337 values_parser = values_parser or (lambda val: val)
338 for key, (_, val) in section_options.items():
339 value[key] = values_parser(val)
340 return value
341
342 def parse_section(self, section_options):
343 """Parses configuration file section.
344
345 :param dict section_options:
346 """
347 for (name, (_, value)) in section_options.items():
348 try:
349 self[name] = value
350
351 except KeyError:
352 pass # Keep silent for a new option may appear anytime.
353
354 def parse(self):
355 """Parses configuration file items from one
356 or more related sections.
357
358 """
359 for section_name, section_options in self.sections.items():
360
361 method_postfix = ''
362 if section_name: # [section.option] variant
363 method_postfix = '_%s' % section_name
364
365 section_parser_method = getattr(
366 self,
367 # Dots in section names are tranlsated into dunderscores.
368 ('parse_section%s' % method_postfix).replace('.', '__'),
369 None)
370
371 if section_parser_method is None:
372 raise DistutilsOptionError(
373 'Unsupported distribution option section: [%s.%s]' % (
374 self.section_prefix, section_name))
375
376 section_parser_method(section_options)
377
378
379 class ConfigMetadataHandler(ConfigHandler):
380
381 section_prefix = 'metadata'
382
383 aliases = {
384 'home_page': 'url',
385 'summary': 'description',
386 'classifier': 'classifiers',
387 'platform': 'platforms',
388 }
389
390 strict_mode = False
391 """We need to keep it loose, to be partially compatible with
392 `pbr` and `d2to1` packages which also uses `metadata` section.
393
394 """
395
396 @property
397 def parsers(self):
398 """Metadata item name to parser function mapping."""
399 parse_list = self._parse_list
400 parse_file = self._parse_file
401
402 return {
403 'platforms': parse_list,
404 'keywords': parse_list,
405 'provides': parse_list,
406 'requires': parse_list,
407 'obsoletes': parse_list,
408 'classifiers': self._get_parser_compound(parse_file, parse_list),
409 'license': parse_file,
410 'description': parse_file,
411 'long_description': parse_file,
412 'version': self._parse_version,
413 }
414
415 def parse_section_classifiers(self, section_options):
416 """Parses configuration file section.
417
418 :param dict section_options:
419 """
420 classifiers = []
421 for begin, (_, rest) in section_options.items():
422 classifiers.append('%s :%s' % (begin.title(), rest))
423
424 self['classifiers'] = classifiers
425
426 def _parse_version(self, value):
427 """Parses `version` option value.
428
429 :param value:
430 :rtype: str
431
432 """
433 version = self._parse_attr(value)
434
435 if callable(version):
436 version = version()
437
438 if not isinstance(version, string_types):
439 if hasattr(version, '__iter__'):
440 version = '.'.join(map(str, version))
441 else:
442 version = '%s' % version
443
444 return version
445
446
447 class ConfigOptionsHandler(ConfigHandler):
448
449 section_prefix = 'options'
450
451 @property
452 def parsers(self):
453 """Metadata item name to parser function mapping."""
454 parse_list = self._parse_list
455 parse_list_semicolon = partial(self._parse_list, separator=';')
456 parse_bool = self._parse_bool
457 parse_dict = self._parse_dict
458
459 return {
460 'zip_safe': parse_bool,
461 'use_2to3': parse_bool,
462 'include_package_data': parse_bool,
463 'package_dir': parse_dict,
464 'use_2to3_fixers': parse_list,
465 'use_2to3_exclude_fixers': parse_list,
466 'convert_2to3_doctests': parse_list,
467 'scripts': parse_list,
468 'eager_resources': parse_list,
469 'dependency_links': parse_list,
470 'namespace_packages': parse_list,
471 'install_requires': parse_list_semicolon,
472 'setup_requires': parse_list_semicolon,
473 'tests_require': parse_list_semicolon,
474 'packages': self._parse_packages,
475 'entry_points': self._parse_file,
476 }
477
478 def _parse_packages(self, value):
479 """Parses `packages` option value.
480
481 :param value:
482 :rtype: list
483 """
484 find_directive = 'find:'
485
486 if not value.startswith(find_directive):
487 return self._parse_list(value)
488
489 # Read function arguments from a dedicated section.
490 find_kwargs = self.parse_section_packages__find(
491 self.sections.get('packages.find', {}))
492
493 from setuptools import find_packages
494
495 return find_packages(**find_kwargs)
496
497 def parse_section_packages__find(self, section_options):
498 """Parses `packages.find` configuration file section.
499
500 To be used in conjunction with _parse_packages().
501
502 :param dict section_options:
503 """
504 section_data = self._parse_section_to_dict(
505 section_options, self._parse_list)
506
507 valid_keys = ['where', 'include', 'exclude']
508
509 find_kwargs = dict(
510 [(k, v) for k, v in section_data.items() if k in valid_keys and v])
511
512 where = find_kwargs.get('where')
513 if where is not None:
514 find_kwargs['where'] = where[0] # cast list to single val
515
516 return find_kwargs
517
518 def parse_section_entry_points(self, section_options):
519 """Parses `entry_points` configuration file section.
520
521 :param dict section_options:
522 """
523 parsed = self._parse_section_to_dict(section_options, self._parse_list)
524 self['entry_points'] = parsed
525
526 def _parse_package_data(self, section_options):
527 parsed = self._parse_section_to_dict(section_options, self._parse_list)
528
529 root = parsed.get('*')
530 if root:
531 parsed[''] = root
532 del parsed['*']
533
534 return parsed
535
536 def parse_section_package_data(self, section_options):
537 """Parses `package_data` configuration file section.
538
539 :param dict section_options:
540 """
541 self['package_data'] = self._parse_package_data(section_options)
542
543 def parse_section_exclude_package_data(self, section_options):
544 """Parses `exclude_package_data` configuration file section.
545
546 :param dict section_options:
547 """
548 self['exclude_package_data'] = self._parse_package_data(
549 section_options)
550
551 def parse_section_extras_require(self, section_options):
552 """Parses `extras_require` configuration file section.
553
554 :param dict section_options:
555 """
556 parse_list = partial(self._parse_list, separator=';')
557 self['extras_require'] = self._parse_section_to_dict(
558 section_options, parse_list)
OLDNEW
« no previous file with comments | « third_party/google-endpoints/setuptools/command/upload_docs.py ('k') | third_party/google-endpoints/setuptools/depends.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698