def all_classifiers(self): classifiers = copy.copy(self.classifiers) # Automatically set python classifiers parser = VersionParser() if self.python_versions == '*': python_constraint = parser.parse_constraints('~2.7 || ^3.4') else: python_constraint = self.python_constraint for version in sorted(self.AVAILABLE_PYTHONS): if len(version) == 1: constraint = parser.parse_constraints(version + '.*') else: constraint = Constraint('=', version) if python_constraint.matches(constraint): classifiers.append( 'Programming Language :: Python :: {}'.format(version)) # Automatically set license classifiers if self.license: classifiers.append(self.license.classifier) classifiers = set(classifiers) return sorted(classifiers)
def test_format_python_constraint(): parser = VersionParser() constraint = parser.parse_constraints('~2.7 || ^3.6') result = format_python_constraint(constraint) assert result == '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*'
def find_packages(self, name: str, constraint: Union[Constraint, str, None] = None, extras: Union[list, None] = None) -> List[Package]: """ Find packages on the remote server. """ packages = [] if constraint is not None and not isinstance(constraint, BaseConstraint): version_parser = VersionParser() constraint = version_parser.parse_constraints(constraint) info = self.get_package_info(name) versions = [] for version, release in info['releases'].items(): if (not constraint or (constraint and constraint.matches(Constraint('=', version)))): versions.append(version) for version in versions: packages.append(self.package(name, version, extras=extras)) return packages
def find_packages(self, name, constraint=None, extras=None, allow_prereleases=False): name = name.lower() packages = [] if extras is None: extras = [] if constraint is None: constraint = '*' if not isinstance(constraint, BaseConstraint): parser = VersionParser() constraint = parser.parse_constraints(constraint) for package in self.packages: if name == package.name: pkg_constraint = Constraint('==', package.version) if constraint is None or constraint.matches(pkg_constraint): for dep in package.requires: for extra in extras: if extra not in package.extras: continue reqs = package.extras[extra] for req in reqs: if req.name == dep.name: dep.activate() packages.append(package) return packages
def find_packages(self, name, constraint=None, extras=None): packages = [] if constraint is not None and not isinstance(constraint, BaseConstraint): version_parser = VersionParser() constraint = version_parser.parse_constraints(constraint) key = name if constraint: key = f'{key}:{str(constraint)}' if self._cache.store('matches').has(key): versions = self._cache.store('matches').get(key) else: candidates = [str(c.version) for c in self._repository.find_all_candidates(name)] versions = [] for version in candidates: if version in versions: continue if ( not constraint or (constraint and constraint.matches(Constraint('=', version))) ): versions.append(version) self._cache.store('matches').put(key, versions, 5) for version in versions: packages.append(self.package(name, version, extras=extras)) return packages
def find_packages(self, name, constraint=None, extras=None): name = name.lower() packages = [] if extras is None: extras = [] if not isinstance(constraint, BaseConstraint): parser = VersionParser() constraint = parser.parse_constraints(constraint) for package in self.packages: if name == package.name: pkg_constraint = Constraint('==', package.version) if constraint is None or constraint.matches(pkg_constraint): for extra in extras: if extra in package.extras: for dep in package.extras[extra]: dep.activate() package.requires += package.extras[extra] packages.append(package) return packages
def format_python_constraint(constraint): """ This helper will help in transforming disjunctive constraint into proper constraint. """ if not isinstance(constraint, MultiConstraint): return str(constraint) has_disjunctive = False for c in constraint.constraints: if isinstance(c, MultiConstraint) and c.is_disjunctive(): has_disjunctive = True break parser = VersionParser() formatted = [] accepted = [] if not constraint.is_disjunctive() and not has_disjunctive: return str(constraint) for version in PYTHON_VERSION: version_constraint = parser.parse_constraints(version) matches = constraint.matches(version_constraint) if not matches: formatted.append('!=' + version) else: accepted.append(version) # Checking lower bound low = accepted[0] formatted.insert(0, '>=' + '.'.join(low.split('.')[:2])) return ', '.join(formatted)
def _filter_operations(self, ops, repo ): # type: (List[Operation], Repository) -> None extra_packages = [p.name for p in self._get_extra_packages(repo)] for op in ops: if isinstance(op, Update): package = op.target_package else: package = op.package if op.job_type == 'uninstall': continue parser = VersionParser() python = '.'.join([str(i) for i in self._venv.version_info[:3]]) if 'python' in package.requirements: python_constraint = parser.parse_constraints( package.requirements['python'] ) if not python_constraint.matches(Constraint('=', python)): # Incompatible python versions op.skip('Not needed for the current python version') continue if not package.python_constraint.matches(Constraint('=', python)): op.skip('Not needed for the current python version') continue if 'platform' in package.requirements: platform_constraint = GenericConstraint.parse( package.requirements['platform'] ) if not platform_constraint.matches( GenericConstraint('=', sys.platform) ): # Incompatible systems op.skip('Not needed for the current platform') continue if self._update: extras = {} for extra, deps in self._package.extras.items(): extras[extra] = [dep.name for dep in deps] else: extras = {} for extra, deps in self._locker.lock_data.get('extras', {}).items(): extras[extra] = [dep.lower() for dep in deps] # If a package is optional and not requested # in any extra we skip it if package.optional: if package.name not in extra_packages: op.skip('Not required') # If the package is a dev package and dev packages # are not requests, we skip it if package.category == 'dev' and not self.is_dev_mode(): op.skip('Dev dependencies not requested')
def classifiers(self): classifiers = [] # Automatically set python classifiers parser = VersionParser() if self.python_versions == '*': python_constraint = parser.parse_constraints('~2.7 || ^3.4') else: python_constraint = self.python_constraint for version in sorted(self.AVAILABLE_PYTHONS): if len(version) == 1: constraint = parser.parse_constraints(version + '.*') else: constraint = Constraint('=', version) if python_constraint.matches(constraint): classifiers.append( f'Programming Language :: Python :: {version}') return classifiers
def handle(self): packages = self.argument('package') if not packages: package = self.poetry.package dependencies = package.requires + package.dev_requires else: requirements = self._determine_requirements(packages) requirements = self._format_requirements(requirements) # validate requirements format parser = VersionParser() for constraint in requirements.values(): parser.parse_constraints(constraint) dependencies = [] for name, constraint in requirements.items(): dependencies.append( Dependency(name, constraint) ) solver = Solver( self.poetry.package, self.poetry.pool, Repository(), self.output ) ops = solver.solve(dependencies) self.line('') self.line('Resolution results:') self.line('') for op in ops: package = op.package self.line(f' - <info>{package.name}</info> ' f'(<comment>{package.version}</comment>)')
def find_packages(self, name, # type: str constraint=None, # type: Union[Constraint, str, None] extras=None, # type: Union[list, None] allow_prereleases=False # type: bool ): # type: (...) -> List[Package] """ Find packages on the remote server. """ if constraint is not None and not isinstance(constraint, BaseConstraint): version_parser = VersionParser() constraint = version_parser.parse_constraints(constraint) info = self.get_package_info(name) packages = [] for version, release in info['releases'].items(): if not release: # Bad release self._log( 'No release information found for {}-{}, skipping'.format( name, version ), level='debug' ) continue package = Package(name, version) if package.is_prerelease() and not allow_prereleases: continue if ( not constraint or (constraint and constraint.matches(Constraint('=', version))) ): if extras is not None: package.requires_extras = extras packages.append(package) self._log( '{} packages found for {} {}'.format( len(packages), name, str(constraint) ), level='debug' ) return packages
def get_classifers(self): classifiers = [] # Automatically set python classifiers parser = VersionParser() if self._package.python_versions == '*': python_constraint = parser.parse_constraints('~2.7 || ^3.4') else: python_constraint = self._package.python_constraint for version in sorted(self.AVAILABLE_PYTHONS): if python_constraint.matches(Constraint('=', version)): classifiers.append( 'Programming Language :: Python :: {}'.format(version)) return classifiers
def _filter_operations(self, ops: List[Operation]): for op in ops: if isinstance(op, Update): package = op.target_package else: package = op.package if op.job_type == 'uninstall': continue parser = VersionParser() python = '.'.join([str(i) for i in sys.version_info[:3]]) if 'python' in package.requirements: python_constraint = parser.parse_constraints( package.requirements['python']) if not python_constraint.matches(Constraint('=', python)): # Incompatible python versions op.skip('Not needed for the current python version') continue if self._update: extras = {} for extra, deps in self._package.extras.items(): extras[extra] = [dep.name for dep in deps] else: extras = {} for extra, deps in self._locker.lock_data.get('extras', {}): extras[extra] = [dep.lower() for dep in deps] # If a package is optional and not requested # in any extra we skip it if package.optional: drop = True for extra in self._extras: if extra in extras and package.name in extras[extra]: drop = False continue if drop: op.skip('Not required')
class Dependency: def __init__(self, name: str, constraint: str, optional: bool = False, category: str = 'main', allows_prereleases: bool = False): self._name = name.lower() self._pretty_name = name self._parser = VersionParser() try: self._constraint = self._parser.parse_constraints(constraint) except ValueError: self._constraint = self._parser.parse_constraints('*') self._pretty_constraint = constraint self._optional = optional self._category = category self._allows_prereleases = allows_prereleases self._python_versions = '*' self._python_constraint = self._parser.parse_constraints('*') self._platform = '*' self._platform_constraint = self._parser.parse_constraints('*') self._extras = [] @property def name(self): return self._name @property def constraint(self): return self._constraint @property def pretty_constraint(self): return self._pretty_constraint @property def pretty_name(self): return self._pretty_name @property def category(self): return self._category @property def python_versions(self): return self._python_versions @python_versions.setter def python_versions(self, value: str): self._python_versions = value self._python_constraint = self._parser.parse_constraints(value) @property def python_constraint(self): return self._python_constraint @property def platform(self) -> str: return self._platform @platform.setter def platform(self, value: str): self._platform = value @property def platform_constraint(self): return self._platform_constraint @property def extras(self) -> list: return self._extras def allows_prereleases(self): return self._allows_prereleases def is_optional(self): return self._optional def is_vcs(self): return False def accepts(self, package: 'poetry.packages.Package') -> bool: """ Determines if the given package matches this dependency. """ return (self._name == package.name and self._constraint.matches(Constraint('=', package.version)) and (not package.is_prerelease() or self.allows_prereleases())) def to_pep_508(self) -> str: requirement = f'{self.pretty_name}' if isinstance(self.constraint, MultiConstraint): requirement += ' ({})'.format(','.join([ str(c).replace(' ', '') for c in self.constraint.constraints ])) else: requirement += ' ({})'.format( str(self.constraint).replace(' ', '')) # Markers markers = [] # Python marker if self.python_versions != '*': python_constraint = self.python_constraint markers.append( self._create_nested_marker('python_version', python_constraint)) if markers: requirement += f'; {" and ".join(markers)}' return requirement def _create_nested_marker(self, name, constraint): if isinstance(constraint, MultiConstraint): parts = [] for c in constraint.constraints: parts.append(self._create_nested_marker(name, c)) glue = ' and ' if constraint.is_disjunctive(): parts = [f'({part})' for part in parts] glue = ' or ' marker = glue.join(parts) else: marker = f'{name}{constraint.string_operator}"{constraint.version}"' return marker def activate(self): """ Set the dependency as mandatory. """ self._optional = False def __eq__(self, other): if not isinstance(other, Dependency): return NotImplemented return self._name == other.name and self._constraint == other.constraint def __hash__(self): return hash((self._name, self._pretty_constraint)) def __str__(self): return f'{self._pretty_name} ({self._pretty_constraint})' def __repr__(self): return f'<Dependency {str(self)}>'
def solve(self, requested, fixed=None) -> List[Operation]: resolver = Resolver(Provider(self._package, self._pool), UI(self._io)) base = None if fixed is not None: base = DependencyGraph() for fixed_req in fixed: base.add_vertex(fixed_req.name, fixed_req, True) try: graph = resolver.resolve(requested, base=base) except ResolverError as e: raise SolverProblemError(e) packages = [v.payload for v in graph.vertices.values()] # Setting info for vertex in graph.vertices.values(): tags = self._get_tags_for_vertex(vertex, requested) if 'main' in tags['category']: vertex.payload.category = 'main' else: vertex.payload.category = 'dev' if not tags['optional']: vertex.payload.optional = False else: vertex.payload.optional = True # Finding the less restrictive requirements requirements = {} parser = VersionParser() for req_name, reqs in tags['requirements'].items(): for req in reqs: if req_name == 'python': if 'python' not in requirements: requirements['python'] = req continue previous = parser.parse_constraints(requirements['python']) current = parser.parse_constraints(req) if current.matches(previous): requirements['python'] = req if req_name == 'platform': if 'platform' not in requirements: requirements['platform'] = req continue vertex.payload.requirements = requirements operations = [] for package in packages: installed = False for pkg in self._locked.packages: if package.name == pkg.name: installed = True # Checking version if package.version != pkg.version: operations.append(Update(pkg, package)) break if not installed: operations.append(Install(package)) # Checking for removals for pkg in self._locked.packages: remove = True for package in packages: if pkg.name == package.name: remove = False break if remove: operations.append(Uninstall(pkg)) return list(reversed(operations))
class Package(object): AVAILABLE_PYTHONS = {'2', '2.7', '3', '3.4', '3.5', '3.6', '3.7'} supported_link_types = { 'require': { 'description': 'requires', 'method': 'requires' }, 'provide': { 'description': 'provides', 'method': 'provides' } } STABILITY_STABLE = 0 STABILITY_RC = 5 STABILITY_BETA = 10 STABILITY_ALPHA = 15 STABILITY_DEV = 20 stabilities = { 'stable': STABILITY_STABLE, 'rc': STABILITY_RC, 'beta': STABILITY_BETA, 'alpha': STABILITY_ALPHA, 'dev': STABILITY_DEV, } def __init__(self, name, version, pretty_version=None): """ Creates a new in memory package. """ self._pretty_name = name self._name = name.lower() self._version = str(parse_version(version)) self._pretty_version = pretty_version or version self.description = '' self._stability = parse_stability(version) self._dev = self._stability == 'dev' self._authors = [] self.homepage = None self.repository_url = None self.keywords = [] self._license = None self.readme = None self.source_type = '' self.source_reference = '' self.source_url = '' self.requires = [] self.dev_requires = [] self.extras = {} self._parser = VersionParser() self.category = 'main' self.hashes = [] self.optional = False # Requirements for making it mandatory self.requirements = {} self.build = None self.include = [] self.exclude = [] self.classifiers = [] self._python_versions = '*' self._python_constraint = self._parser.parse_constraints('*') self._platform = '*' self._platform_constraint = EmptyConstraint() self.cwd = None @property def name(self): return self._name @property def pretty_name(self): return self._pretty_name @property def version(self): return self._version @property def pretty_version(self): return self._pretty_version @property def unique_name(self): return self.name + '-' + self._version @property def pretty_string(self): return self.pretty_name + ' ' + self.pretty_version @property def full_pretty_version(self): if not self._dev and self.source_type not in ['hg', 'git']: return self._pretty_version # if source reference is a sha1 hash -- truncate if len(self.source_reference) == 40: return '{} {}'.format(self._pretty_version, self.source_reference[0:7]) return '{} {}'.format(self._pretty_version, self.source_reference) @property def authors(self): # type: () -> list return self._authors @property def author_name(self): # type: () -> str return self._get_author()['name'] @property def author_email(self): # type: () -> str return self._get_author()['email'] def _get_author(self): # type: () -> dict if not self._authors: return {'name': None, 'email': None} m = AUTHOR_REGEX.match(self._authors[0]) name = m.group('name') email = m.group('email') return {'name': name, 'email': email} @property def python_versions(self): return self._python_versions @python_versions.setter def python_versions(self, value): self._python_versions = value self._python_constraint = self._parser.parse_constraints(value) @property def python_constraint(self): return self._python_constraint @property def platform(self): # type: () -> str return self._platform @platform.setter def platform(self, value): # type: (str) -> None self._platform = value self._platform_constraint = GenericConstraint.parse(value) @property def platform_constraint(self): return self._platform_constraint @property def license(self): return self._license @license.setter def license(self, value): if value is None: self._license = value elif isinstance(value, License): self._license = value else: self._license = license_by_id(value) @property def all_classifiers(self): classifiers = copy.copy(self.classifiers) # Automatically set python classifiers parser = VersionParser() if self.python_versions == '*': python_constraint = parser.parse_constraints('~2.7 || ^3.4') else: python_constraint = self.python_constraint for version in sorted(self.AVAILABLE_PYTHONS): if len(version) == 1: constraint = parser.parse_constraints(version + '.*') else: constraint = Constraint('=', version) if python_constraint.matches(constraint): classifiers.append( 'Programming Language :: Python :: {}'.format(version)) # Automatically set license classifiers if self.license: classifiers.append(self.license.classifier) classifiers = set(classifiers) return sorted(classifiers) def is_dev(self): return self._dev def is_prerelease(self): return self._stability != 'stable' def add_dependency( self, name, # type: str constraint=None, # type: Union[str, dict, None] category='main' # type: str ): # type: (...) -> Dependency if constraint is None: constraint = '*' if isinstance(constraint, dict): optional = constraint.get('optional', False) python_versions = constraint.get('python') platform = constraint.get('platform') allows_prereleases = constraint.get('allows-prereleases', False) if 'git' in constraint: # VCS dependency dependency = VCSDependency( name, 'git', constraint['git'], branch=constraint.get('branch', None), tag=constraint.get('tag', None), rev=constraint.get('rev', None), optional=optional, ) if python_versions: dependency.python_versions = python_versions if platform: dependency.platform = platform elif 'file' in constraint: file_path = Path(constraint['file']) dependency = FileDependency(file_path, base=self.cwd) else: version = constraint['version'] dependency = Dependency(name, version, optional=optional, category=category, allows_prereleases=allows_prereleases) if python_versions: dependency.python_versions = python_versions if platform: dependency.platform = platform if 'extras' in constraint: for extra in constraint['extras']: dependency.extras.append(extra) else: dependency = Dependency(name, constraint, category=category) if category == 'dev': self.dev_requires.append(dependency) else: self.requires.append(dependency) return dependency def __hash__(self): return hash((self._name, self._version)) def __eq__(self, other): if not isinstance(other, Package): return NotImplemented return self._name == other.name and self._version == other.version def __str__(self): return self.unique_name def __repr__(self): return '<Package {}>'.format(self.unique_name)
def handle(self): from poetry.installation import Installer from poetry.semver.version_parser import VersionParser packages = self.argument('name') is_dev = self.option('dev') if (self.option('git') or self.option('path') or self.option('extras')) and len(packages) > 1: raise ValueError( 'You can only specify one package ' 'when using the --git or --path options' ) if self.option('git') and self.option('path'): raise RuntimeError( '--git and --path cannot be used at the same time' ) section = 'dependencies' if is_dev: section = 'dev-dependencies' original_content = self.poetry.file.read() content = self.poetry.file.read() poetry_content = content['tool']['poetry'] for name in packages: for key in poetry_content[section]: if key.lower() == name.lower(): raise ValueError( 'Package {} is already present'.format(name) ) if self.option('git') or self.option('path'): requirements = { packages[0]: '' } else: requirements = self._determine_requirements( packages, allow_prereleases=self.option('allow-prereleases') ) requirements = self._format_requirements(requirements) # validate requirements format parser = VersionParser() for constraint in requirements.values(): parser.parse_constraints(constraint) for name, constraint in requirements.items(): constraint = { 'version': constraint } if self.option('git'): del constraint['version'] constraint['git'] = self.option('git') elif self.option('path'): del constraint['version'] constraint['path'] = self.option('path') if self.option('optional'): constraint['optional'] = True if self.option('allow-prereleases'): constraint['allows-prereleases'] = True if self.option('extras'): constraint['extras'] = self.option('extras') if len(constraint) == 1 and 'version' in constraint: constraint = constraint['version'] poetry_content[section][name] = constraint # Write new content self.poetry.file.write(content) # Cosmetic new line self.line('') # Update packages self.reset_poetry() installer = Installer( self.output, self.venv, self.poetry.package, self.poetry.locker, self.poetry.pool ) installer.dry_run(self.option('dry-run')) installer.update(True) installer.whitelist(requirements) try: status = installer.run() except Exception: self.poetry.file.write(original_content) raise if status != 0 or self.option('dry-run'): # Revert changes if not self.option('dry-run'): self.error( '\n' 'Addition failed, reverting pyproject.toml ' 'to its original content.' ) self.poetry.file.write(original_content) return status
class Package: supported_link_types = { 'require': { 'description': 'requires', 'method': 'requires' }, 'provide': { 'description': 'provides', 'method': 'provides' } } STABILITY_STABLE = 0 STABILITY_RC = 5 STABILITY_BETA = 10 STABILITY_ALPHA = 15 STABILITY_DEV = 20 stabilities = { 'stable': STABILITY_STABLE, 'rc': STABILITY_RC, 'beta': STABILITY_BETA, 'alpha': STABILITY_ALPHA, 'dev': STABILITY_DEV, } def __init__(self, name, version, pretty_version=None): """ Creates a new in memory package. """ self._pretty_name = name self._name = name.lower() self._version = version self._pretty_version = pretty_version or version self.description = '' self._stability = parse_stability(version) self._dev = self._stability == 'dev' self._authors = [] self.homepage = None self.repository_url = None self.keywords = [] self.license = None self.readme = '' self.source_type = '' self.source_reference = '' self.source_url = '' self.requires = [] self.dev_requires = [] self.extras = {} self._parser = VersionParser() self.category = 'main' self.hashes = [] self.optional = False # Requirements for making it mandatory self.requirements = {} self._python_versions = '*' self._python_constraint = self._parser.parse_constraints('*') self._platform = '*' self._platform_constraint = self._parser.parse_constraints('*') @property def name(self): return self._name @property def pretty_name(self): return self._pretty_name @property def version(self): return self._version @property def pretty_version(self): return self._pretty_version @property def unique_name(self): return self.name + '-' + self._version @property def pretty_string(self): return self.pretty_name + ' ' + self.pretty_version @property def full_pretty_version(self): if not self._dev and self.source_type not in ['hg', 'git']: return self._pretty_version # if source reference is a sha1 hash -- truncate if len(self.source_reference) == 40: return '{} {}'.format(self._pretty_version, self.source_reference[0:7]) return '{} {}'.format(self._pretty_version, self.source_reference) @property def authors(self) -> list: return self._authors @property def python_versions(self): return self._python_versions @python_versions.setter def python_versions(self, value: str): self._python_versions = value self._python_constraint = self._parser.parse_constraints(value) @property def python_constraint(self): return self._python_constraint @property def platform(self) -> str: return self._platform @platform.setter def platform(self, value: str): self._platform = value self._platform_constraint = self._parser.parse_constraints(value) @property def platform_constraint(self): return self._platform_constraint def is_dev(self): return self._dev def is_prerelease(self): return self._stability != 'stable' def add_dependency(self, name: str, constraint: Union[str, dict, None] = None, category: str = 'main') -> Dependency: if constraint is None: constraint = '*' if isinstance(constraint, dict): if 'git' in constraint: # VCS dependency optional = constraint.get('optional', False) python_versions = constraint.get('python') platform = constraint.get('platform') dependency = VCSDependency( name, 'git', constraint['git'], branch=constraint.get('branch', None), tag=constraint.get('tag', None), rev=constraint.get('rev', None), optional=optional, ) if python_versions: dependency.python_versions = python_versions if platform: dependency.platform = platform else: version = constraint['version'] optional = constraint.get('optional', False) allows_prereleases = constraint.get('allows_prereleases', False) python_versions = constraint.get('python') platform = constraint.get('platform') dependency = Dependency(name, version, optional=optional, category=category, allows_prereleases=allows_prereleases) if python_versions: dependency.python_versions = python_versions if platform: dependency.platform = platform if 'extras' in constraint: for extra in constraint['extras']: dependency.extras.append(extra) else: dependency = Dependency(name, constraint, category=category) if category == 'dev': self.dev_requires.append(dependency) else: self.requires.append(dependency) return dependency def __hash__(self): return hash((self._name, self._version)) def __eq__(self, other): if not isinstance(other, Package): return NotImplemented return self._name == other.name and self._version == other.version def __str__(self): return self.unique_name def __repr__(self): return '<Package {}>'.format(self.unique_name)
def _get_tags_for_vertex(self, vertex, requested): category = 'dev' optional = True python_version = None platform = None if not vertex.incoming_edges: # Original dependency for req in requested: if vertex.payload.name == req.name: category = req.category optional = req.is_optional() python_version = str(req.python_constraint) platform = str(req.platform_constraint) break return category, optional, python_version, platform parser = VersionParser() python_versions = [] platforms = [] for edge in vertex.incoming_edges: python_version = None platform = None for req in edge.origin.payload.requires: if req.name == vertex.payload.name: python_version = req.python_versions platform = req.platform break (top_category, top_optional, top_python_version, top_platform) = self._get_tags_for_vertex( edge.origin, requested ) if top_category == 'main': category = top_category optional = optional and top_optional # Take the most restrictive constraints if top_python_version is not None: if python_version is not None: previous = parser.parse_constraints(python_version) current = parser.parse_constraints(top_python_version) if top_python_version != '*' and previous.matches(current): python_versions.append(top_python_version) else: python_versions.append(python_version) else: python_versions.append(top_python_version) elif python_version is not None: python_versions.append(python_version) if top_platform is not None: if platform is not None: previous = GenericConstraint.parse(platform) current = GenericConstraint.parse(top_platform) if top_platform != '*' and previous.matches(current): platforms.append(top_platform) else: platforms.append(platform) else: platforms.append(top_platform) elif platform is not None: platforms.append(platform) if not python_versions: python_version = None else: # Find the least restrictive constraint python_version = python_versions[0] previous = parser.parse_constraints(python_version) for constraint in python_versions[1:]: current = parser.parse_constraints(constraint) if python_version == '*': continue elif constraint == '*': python_version = constraint elif current.matches(previous): python_version = constraint if not platforms: platform = None else: platform = platforms[0] previous = GenericConstraint.parse(platform) for constraint in platforms[1:]: current = GenericConstraint.parse(constraint) if platform == '*': continue elif constraint == '*': platform = constraint elif current.matches(previous): platform = constraint return category, optional, python_version, platform
class Dependency(object): def __init__(self, name, # type: str constraint, # type: str optional=False, # type: bool category='main', # type: str allows_prereleases=False # type: bool ): self._name = canonicalize_name(name) self._pretty_name = name self._parser = VersionParser() try: if not isinstance(constraint, BaseConstraint): self._constraint = self._parser.parse_constraints(constraint) else: self._constraint = constraint except ValueError: self._constraint = self._parser.parse_constraints('*') self._pretty_constraint = constraint self._optional = optional self._category = category self._allows_prereleases = allows_prereleases self._python_versions = '*' self._python_constraint = self._parser.parse_constraints('*') self._platform = '*' self._platform_constraint = EmptyConstraint() self._extras = [] self._in_extras = [] @property def name(self): return self._name @property def constraint(self): return self._constraint @property def pretty_constraint(self): return self._pretty_constraint @property def pretty_name(self): return self._pretty_name @property def category(self): return self._category @property def python_versions(self): return self._python_versions @python_versions.setter def python_versions(self, value): self._python_versions = value self._python_constraint = self._parser.parse_constraints(value) @property def python_constraint(self): return self._python_constraint @property def platform(self): return self._platform @platform.setter def platform(self, value): self._platform = value self._platform_constraint = GenericConstraint.parse(value) @property def platform_constraint(self): return self._platform_constraint @property def extras(self): # type: () -> list return self._extras @property def in_extras(self): # type: () -> list return self._in_extras def allows_prereleases(self): return self._allows_prereleases def is_optional(self): return self._optional def is_vcs(self): return False def is_file(self): return False def is_directory(self): return False def accepts(self, package): # type: (poetry.packages.Package) -> bool """ Determines if the given package matches this dependency. """ return ( self._name == package.name and self._constraint.matches(Constraint('=', package.version)) and (not package.is_prerelease() or self.allows_prereleases()) ) def to_pep_508(self, with_extras=True): # type: (bool) -> str requirement = self.pretty_name if self.extras: requirement += '[{}]'.format(','.join(self.extras)) if isinstance(self.constraint, MultiConstraint): requirement += ' ({})'.format(','.join( [str(c).replace(' ', '') for c in self.constraint.constraints] )) elif str(self.constraint) != '*': requirement += ' ({})'.format(str(self.constraint).replace(' ', '')) # Markers markers = [] # Python marker if self.python_versions != '*': python_constraint = self.python_constraint markers.append( self._create_nested_marker('python_version', python_constraint) ) in_extras = ' || '.join(self._in_extras) if in_extras and with_extras: markers.append( self._create_nested_marker( 'extra', GenericConstraint.parse(in_extras) ) ) if markers: if len(markers) > 1: markers = ['({})'.format(m) for m in markers] requirement += '; {}'.format(' and '.join(markers)) else: requirement += '; {}'.format(markers[0]) return requirement def _create_nested_marker(self, name, constraint): if isinstance(constraint, MultiConstraint): parts = [] for c in constraint.constraints: multi = False if isinstance(c, MultiConstraint): multi = True parts.append((multi, self._create_nested_marker(name, c))) glue = ' and ' if constraint.is_disjunctive(): parts = [ '({})'.format(part[1]) if part[0] else part[1] for part in parts ] glue = ' or ' else: parts = [part[1] for part in parts] marker = glue.join(parts) else: marker = '{} {} "{}"'.format( name, constraint.string_operator, constraint.version ) return marker def activate(self): """ Set the dependency as mandatory. """ self._optional = False def deactivate(self): """ Set the dependency as optional. """ self._optional = True def __eq__(self, other): if not isinstance(other, Dependency): return NotImplemented return self._name == other.name and self._constraint == other.constraint def __hash__(self): return hash((self._name, self._pretty_constraint)) def __str__(self): return '{} ({})'.format( self._pretty_name, self._pretty_constraint ) def __repr__(self): return '<{} {}>'.format(self.__class__.__name__, str(self))
def handle(self): packages = self.argument('name') is_dev = self.option('dev') section = 'dependencies' if is_dev: section = 'dev-dependencies' original_content = self.poetry.file.read() content = self.poetry.file.read() poetry_content = content['tool']['poetry'] for name in packages: for key in poetry_content[section]: if key.lower() == name.lower(): raise ValueError(f'Package {name} is already present') requirements = self._determine_requirements(packages) requirements = self._format_requirements(requirements) # validate requirements format parser = VersionParser() for constraint in requirements.values(): parser.parse_constraints(constraint) for name, constraint in requirements.items(): if self.option('optional'): constraint = {'version': constraint, 'optional': True} poetry_content[section][name] = constraint # Write new content self.poetry.file.write(content) # Cosmetic new line self.line('') # Update packages self.reset_poetry() installer = Installer(self.output, self.venv, self.poetry.package, self.poetry.locker, self.poetry.pool) installer.dry_run(self.option('dry-run')) installer.update(True) installer.whitelist(requirements) try: status = installer.run() except Exception: self.poetry.file.write(original_content) raise if status != 0 or self.option('dry-run'): # Revert changes if not self.option('dry-run'): self.error('\n' 'Addition failed, reverting pyproject.toml ' 'to its original content.') self.poetry.file.write(original_content) return status