Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

datatype()s can declare default values #6374

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions src/python/pants/base/project_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,19 +212,10 @@ def path(self):
class File(datatype(['path']), Stat):
"""A file."""

def __new__(cls, path):
return super(File, cls).__new__(cls, path)


class Dir(datatype(['path']), Stat):
"""A directory."""

def __new__(cls, path):
return super(Dir, cls).__new__(cls, path)


class Link(datatype(['path']), Stat):
"""A symbolic link."""

def __new__(cls, path):
return super(Link, cls).__new__(cls, path)
9 changes: 5 additions & 4 deletions src/python/pants/base/specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from abc import abstractmethod

from pants.util.meta import AbstractClass
from pants.util.objects import DatatypeFieldDecl as F
from pants.util.objects import datatype


Expand Down Expand Up @@ -60,11 +61,11 @@ def to_spec_string(self):
return '{}^'.format(self.directory)


class Specs(datatype(['dependencies', 'tags', ('exclude_patterns', tuple)])):
class Specs(datatype([
'dependencies',
F('tags', tuple, default_value=()),
F('exclude_patterns', tuple, default_value=())])):
"""A collection of Specs representing Spec subclasses, tags and regex filters."""

def __new__(cls, dependencies, tags=tuple(), exclude_patterns=tuple()):
return super(Specs, cls).__new__(cls, dependencies, tags, exclude_patterns)

def exclude_patterns_memo(self):
return [re.compile(pattern) for pattern in set(self.exclude_patterns or [])]
42 changes: 15 additions & 27 deletions src/python/pants/engine/isolated_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from pants.engine.fs import DirectoryDigest
from pants.engine.rules import RootRule, rule
from pants.engine.selectors import Select
from pants.util.objects import DatatypeFieldDecl as F
from pants.util.objects import Exactly, TypeCheckError, datatype


Expand All @@ -23,28 +24,25 @@ class ExecuteProcessRequest(datatype([
('argv', tuple),
('input_files', DirectoryDigest),
('description', text_type),
('env', tuple),
('output_files', tuple),
('output_directories', tuple),
# TODO: allow inferring a default value if a type is provided which has a 0-arg constructor.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be happy to just remove this TODO; inferring defaults is scary...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussion on slack, agreed and will do.

F('env', Exactly(tuple, dict, type(None)), default_value=None),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this Noneable? I would remove type(None) from its type constraints, and default it to () or {}...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like a correct analysis and I will do that.

F('output_files', tuple, default_value=()),
F('output_directories', tuple, default_value=()),
# NB: timeout_seconds covers the whole remote operation including queuing and setup.
('timeout_seconds', Exactly(float, int)),
('jdk_home', Exactly(text_type, type(None))),
F('timeout_seconds', Exactly(float, int), default_value=_default_timeout_seconds),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inline _default_timeout_seconds?

F('jdk_home', Exactly(text_type, type(None)), default_value=None),
])):
"""Request for execution with args and snapshots to extract."""

def __new__(
cls,
argv,
input_files,
description,
env=None,
output_files=(),
output_directories=(),
timeout_seconds=_default_timeout_seconds,
jdk_home=None,
):
def __new__(cls, *args, **kwargs):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this this implementation to be more work for me to read, rather than less... Before it massaged param, and then constructed an object as I'd expect; now it has multiple layers of validation, and the provision on the type definition for an intermediate state which would be invalid outside the constructor (having a dict as a field value)...

Maybe the answer here is to fall back to the automatically implemented __new__, and have a with_env class-function which does the env munging and passes the tuple to the constructor?

this_object = super(ExecuteProcessRequest, cls).__new__(cls, *args, **kwargs)

env = this_object.env
# `env` is a tuple, a dict, or None.
if env is None:
env = ()
elif isinstance(env, tuple):
pass
else:
if not isinstance(env, dict):
raise TypeCheckError(
Expand All @@ -56,17 +54,7 @@ def __new__(
)
env = tuple(item for pair in env.items() for item in pair)

return super(ExecuteProcessRequest, cls).__new__(
cls,
argv=argv,
env=env,
input_files=input_files,
description=description,
output_files=output_files,
output_directories=output_directories,
timeout_seconds=timeout_seconds,
jdk_home=jdk_home,
)
return this_object.copy(env=env)


class ExecuteProcessResult(datatype([('stdout', binary_type),
Expand Down
22 changes: 10 additions & 12 deletions src/python/pants/engine/selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
import six

from pants.util.meta import AbstractClass
from pants.util.objects import Exactly, datatype
from pants.util.objects import DatatypeFieldDecl as F
from pants.util.objects import Exactly, SubclassesOf, datatype


def type_or_constraint_repr(constraint):
Expand Down Expand Up @@ -96,23 +97,25 @@ def product(self):
"""The product that this selector produces."""


class Select(datatype(['product', 'optional']), Selector):
class Select(datatype([
'product',
F('optional', bool, default_value=False),
]), Selector):
"""Selects the given Product for the Subject provided to the constructor.

If optional=True and no matching product can be produced, will return None.
"""

def __new__(cls, product, optional=False):
obj = super(Select, cls).__new__(cls, product, optional)
return obj

def __repr__(self):
return '{}({}{})'.format(type(self).__name__,
type_or_constraint_repr(self.product),
', optional=True' if self.optional else '')


class SelectVariant(datatype(['product', 'variant_key']), Selector):
class SelectVariant(datatype([
'product',
('variant_key', SubclassesOf(*six.string_types)),
]), Selector):
"""Selects the matching Product and variant name for the Subject provided to the constructor.

For example: a SelectVariant with a variant_key of "thrift" and a product of type ApacheThrift
Expand All @@ -121,11 +124,6 @@ class SelectVariant(datatype(['product', 'variant_key']), Selector):
"""
optional = False

def __new__(cls, product, variant_key):
if not isinstance(variant_key, six.string_types):
raise ValueError('Expected variant_key to be a string, but was {!r}'.format(variant_key))
return super(SelectVariant, cls).__new__(cls, product, variant_key)

def __repr__(self):
return '{}({}, {})'.format(type(self).__name__,
type_or_constraint_repr(self.product),
Expand Down
10 changes: 3 additions & 7 deletions src/python/pants/option/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@

from __future__ import absolute_import, division, print_function, unicode_literals

from collections import namedtuple
from pants.util.objects import DatatypeFieldDecl as F
from pants.util.objects import datatype


GLOBAL_SCOPE = ''
GLOBAL_SCOPE_CONFIG_SECTION = 'GLOBAL'


# FIXME: convert this to a datatype?
class ScopeInfo(namedtuple('_ScopeInfo', ['scope', 'category', 'optionable_cls'])):
class ScopeInfo(datatype(['scope', 'category', F('optionable_cls', default_value=None)])):
"""Information about a scope."""

# Symbolic constants for different categories of scope.
Expand All @@ -36,7 +36,3 @@ def deprecated_scope_removal_version(self):

def _optionable_cls_attr(self, name, default=None):
return getattr(self.optionable_cls, name) if self.optionable_cls else default


# Allow the optionable_cls to default to None.
ScopeInfo.__new__.__defaults__ = (None, )
Loading