def as_tryte_strings(self, head_to_tail=False):
        # type: (bool) -> List[TransactionTrytes]
        """
        Returns TryteString representations of the transactions in this
        bundle.

        :param head_to_tail:
            Determines the order of the transactions:

            - ``True``: head txn first, tail txn last.
            - ``False`` (default): tail txn first, head txn last.

            Note that the order is reversed by default, as this is the
            way bundles are typically broadcast to the Tangle.
        """
        transactions = self if head_to_tail else reversed(self)
        return [t.as_tryte_string() for t in transactions]

def as_tryte_strings ( self , head_to_tail = False ) : transactions = self if head_to_tail else reversed ( self ) return [ t . as_tryte_string ( ) for t in transactions ]

Returns TryteString representations of the transactions in this bundle .


def group_transactions(self):
        # type: () -> List[List[Transaction]]
        """
        Groups transactions in the bundle by address.
        """
        groups = []

        if self:
            last_txn = self.tail_transaction
            current_group = [last_txn]
            for current_txn in self.transactions[1:]:
                # Transactions are grouped by address, so as long as the
                # address stays consistent from one transaction to
                # another, we are still in the same group.
                if current_txn.address == last_txn.address:
                    current_group.append(current_txn)
                else:
                    groups.append(current_group)
                    current_group = [current_txn]

                last_txn = current_txn

            if current_group:
                groups.append(current_group)

        return groups

def group_transactions ( self ) : groups = [ ] if self : last_txn = self . tail_transaction current_group = [ last_txn ] for current_txn in self . transactions [ 1 : ] : if current_txn . address == last_txn . address : current_group . append ( current_txn ) else : groups . append ( current_group ) current_group = [ current_txn ] last_txn = current_txn if current_group : groups . append ( current_group ) return groups

Groups transactions in the bundle by address .


def discover_commands(package, recursively=True):
  # type: (Union[ModuleType, Text], bool) -> Dict[Text, 'CommandMeta']
  """
  Automatically discover commands in the specified package.

  :param package:
    Package path or reference.

  :param recursively:
    If True, will descend recursively into sub-packages.

  :return:
    All commands discovered in the specified package, indexed by
    command name (note: not class name).
  """
  # http://stackoverflow.com/a/25562415/
  if isinstance(package, string_types):
    package = import_module(package) # type: ModuleType

  commands = {}

  for _, name, is_package in walk_packages(package.__path__, package.__name__ + '.'):
    # Loading the module is good enough; the CommandMeta metaclass will
    # ensure that any commands in the module get registered.

    # Prefix in name module move to function "walk_packages" for fix
    # conflict with names importing packages
    # Bug https://github.com/iotaledger/iota.lib.py/issues/63
    sub_package = import_module(name)

    # Index any command classes that we find.
    for (_, obj) in get_members(sub_package):
      if is_class(obj) and isinstance(obj, CommandMeta):
        command_name = getattr(obj, 'command')
        if command_name:
          commands[command_name] = obj

    if recursively and is_package:
      commands.update(discover_commands(sub_package))

  return commands

def discover_commands ( package , recursively = True ) : if isinstance ( package , string_types ) : package = import_module ( package ) commands = { } for _ , name , is_package in walk_packages ( package . __path__ , package . __name__ + '.' ) : sub_package = import_module ( name ) for ( _ , obj ) in get_members ( sub_package ) : if is_class ( obj ) and isinstance ( obj , CommandMeta ) : command_name = getattr ( obj , 'command' ) if command_name : commands [ command_name ] = obj if recursively and is_package : commands . update ( discover_commands ( sub_package ) ) return commands

Automatically discover commands in the specified package .


def _execute(self, request):
    # type: (dict) -> dict
    """
    Sends the request object to the adapter and returns the response.

    The command name will be automatically injected into the request
    before it is sent (note: this will modify the request object).
    """
    request['command'] = self.command
    return self.adapter.send_request(request)

def _execute ( self , request ) : request [ 'command' ] = self . command return self . adapter . send_request ( request )

Sends the request object to the adapter and returns the response .


def _apply_filter(value, filter_, failure_message):
    # type: (dict, Optional[f.BaseFilter], Text) -> dict
    """
    Applies a filter to a value.  If the value does not pass the
    filter, an exception will be raised with lots of contextual info
    attached to it.
    """
    if filter_:
      runner = f.FilterRunner(filter_, value)

      if runner.is_valid():
        return runner.cleaned_data
      else:
        raise with_context(
          exc = ValueError(
            '{message} ({error_codes}) '
            '(`exc.context["filter_errors"]` '
            'contains more information).'.format(
              message     = failure_message,
              error_codes = runner.error_codes,
            ),
          ),

          context = {
            'filter_errors': runner.get_errors(with_context=True),
          },
        )

    return value

def
_apply_filter
(
value
,
filter_
,
failure_message
)
:
if
filter_
:
runner
=
f
.
FilterRunner
(
filter_
,
value
)
if
runner
.
is_valid
(
)
:
return
runner
.
cleaned_data
else
:
raise
with_context
(
exc
=
ValueError
(
'{message} ({error_codes}) '
'(`exc.context["filter_errors"]` '
'contains more information).'
.
format
(
message
=
failure_message
,
error_codes
=
runner
.
error_codes
,
)
,
)
,
context
=
{
'filter_errors'
:
runner
.
get_errors
(
with_context
=
True
)
,
}
,
)
return
value

Applies a filter to a value . If the value does not pass the filter an exception will be raised with lots of contextual info attached to it .


def get_jobs_url(self, job_id):
        # type: (Text) -> Text
        """
        Returns the URL to check job status.

        :param job_id:
            The ID of the job to check.
        """
        return compat.urllib_parse.urlunsplit((
            self.uri.scheme,
            self.uri.netloc,
            self.uri.path.rstrip('/') + '/jobs/' + job_id,
            self.uri.query,
            self.uri.fragment,
        ))

def get_jobs_url ( self , job_id ) : return compat . urllib_parse . urlunsplit ( ( self . uri . scheme , self . uri . netloc , self . uri . path . rstrip ( '/' ) + '/jobs/' + job_id , self . uri . query , self . uri . fragment , ) )


def errors(self):
        # type: () -> List[Text]
        """
        Returns all errors found with the bundle.
        """
        try:
            self._errors.extend(self._validator)  # type: List[Text]
        except StopIteration:
            pass

        return self._errors

def errors ( self ) : try : self . _errors . extend ( self . _validator ) except StopIteration : pass return self . _errors

Returns all errors found with the bundle .


def is_valid(self):
        # type: () -> bool
        """
        Returns whether the bundle is valid.
        """
        if not self._errors:
            try:
                # We only have to check for a single error to determine
                # if the bundle is valid or not.
                self._errors.append(next(self._validator))
            except StopIteration:
                pass

        return not self._errors

def is_valid ( self ) : if not self . _errors : try : self . _errors . append ( next ( self . _validator ) ) except StopIteration : pass return not self . _errors


def _create_validator(self):
        # type: () -> Generator[Text, None, None]
        """
        Creates a generator that does all the work.
        """
        # Group transactions by address to make it easier to iterate
        # over inputs.
        grouped_transactions = self.bundle.group_transactions()

        # Define a few expected values.
        bundle_hash = self.bundle.hash
        last_index = len(self.bundle) - 1

        # Track a few others as we go along.
        balance = 0

        # Check indices and balance first.
        # Note that we use a counter to keep track of the current index,
        # since at this point we can't trust that the transactions have
        # correct ``current_index`` values.
        counter = 0
        for group in grouped_transactions:
            for txn in group:
                balance += txn.value

                if txn.bundle_hash != bundle_hash:
                    yield 'Transaction {i} has invalid bundle hash.'.format(
                        i=counter,
                    )

                if txn.current_index != counter:
                    yield (
                        'Transaction {i} has invalid current index value '
                        '(expected {i}, actual {actual}).'.format(
                            actual=txn.current_index,
                            i=counter,
                        )
                    )

                if txn.last_index != last_index:
                    yield (
                        'Transaction {i} has invalid last index value '
                        '(expected {expected}, actual {actual}).'.format(
                            actual=txn.last_index,
                            expected=last_index,
                            i=counter,
                        )
                    )

                counter += 1

        # Bundle must be balanced (spends must match inputs).
        if balance != 0:
            yield (
                'Bundle has invalid balance '
                '(expected 0, actual {actual}).'.format(
                    actual=balance,
                )
            )

        # Signature validation is only meaningful if the transactions
        # are otherwise valid.
        if not self._errors:
            signature_validation_queue = []  # type: List[List[Transaction]]

            for group in grouped_transactions:
                # Signature validation only applies to inputs.
                if group[0].value >= 0:
                    continue

                validate_group_signature = True
                for j, txn in enumerate(group):
                    if (j > 0) and (txn.value != 0):
                        # Input is malformed; signature fragments after
                        # the first should have zero value.
                        yield (
                            'Transaction {i} has invalid value '
                            '(expected 0, actual {actual}).'.format(
                                actual=txn.value,

                                # If we get to this point, we know that
                                # the ``current_index`` value for each
                                # transaction can be trusted.
                                i=txn.current_index,
                            )
                        )

                        # We won't be able to validate the signature,
                        # but continue anyway, so that we can check that
                        # the other transactions in the group have the
                        # correct ``value``.
                        validate_group_signature = False
                        continue

                # After collecting the signature fragment from each
                # transaction in the group, queue them up to run through
                # the validator.
                #
                # We have to perform signature validation separately so
                # that we can try different algorithms (for
                # backwards-compatibility).
                #
                # References:
                #
                # - https://github.com/iotaledger/kerl#kerl-integration-in-iota
                if validate_group_signature:
                    signature_validation_queue.append(group)

            # Once we've finished checking the attributes from each
            # transaction in the bundle, go back and validate
            # signatures.
            if signature_validation_queue:
                # ``yield from`` is an option here, but for
                # compatibility with Python 2 clients, we will do it the
                # old-fashioned way.
                for error in self._get_bundle_signature_errors(
                        signature_validation_queue
                ):
                    yield error

Creates a generator that does all the work .


def _get_bundle_signature_errors(self, groups):
        # type: (List[List[Transaction]]) -> List[Text]
        """
        Validates the signature fragments in the bundle.

        :return:
            List of error messages.
            If empty, signature fragments are valid.
        """
        # Start with the currently-supported hash algo.
        current_pos = None
        current_errors = []
        for current_pos, group in enumerate(groups):
            error = self._get_group_signature_error(group, SUPPORTED_SPONGE)
            if error:
                current_errors.append(error)

                # Pause and retry with the legacy algo.
                break

        # If validation failed, then go back and try with the legacy
        # algo (only applies if we are currently transitioning to a new
        # algo).
        if current_errors and LEGACY_SPONGE:
            for group in groups:
                # noinspection PyTypeChecker
                if self._get_group_signature_error(group, LEGACY_SPONGE):
                    # Legacy algo doesn't work, either; no point in
                    # continuing.
                    break
            else:
                # If we get here, then we were able to validate the
                # signature fragments successfully using the legacy
                # algorithm.
                return []

        # If we get here, then validation also failed when using the
        # legacy algorithm.

        # At this point, we know that the bundle is invalid, but we will
        # continue validating with the supported algorithm anyway, so
        # that we can return an error message for every invalid input.
        current_errors.extend(filter(None, (
            self._get_group_signature_error(group, SUPPORTED_SPONGE)
            for group in groups[current_pos + 1:]
        )))

        return current_errors

def _get_bundle_signature_errors ( self , groups ) : current_pos = None current_errors = [ ] for current_pos , group in enumerate ( groups ) : error = self . _get_group_signature_error ( group , SUPPORTED_SPONGE ) if error : current_errors . append ( error ) break if current_errors and LEGACY_SPONGE : for group in groups : if self . _get_group_signature_error ( group , LEGACY_SPONGE ) : break else : return [ ] current_errors . extend ( filter ( None , ( self . _get_group_signature_error ( group , SUPPORTED_SPONGE ) for group in groups [ current_pos + 1 : ] ) ) ) return current_errors

Validates the signature fragments in the bundle .


def _get_group_signature_error(group, sponge_type):
        # type: (List[Transaction], type) -> Optional[Text]
        """
        Validates the signature fragments for a group of transactions
        using the specified sponge type.

        Note: this method assumes that the transactions in the group
        have already passed basic validation (see
        :py:meth:`_create_validator`).

        :return:
          - ``None``:  Indicates that the signature fragments are valid.
          - ``Text``:  Error message indicating the fragments are invalid.
        """
        validate_group_signature = validate_signature_fragments(
            fragments=[txn.signature_message_fragment for txn in group],
            hash_=group[0].bundle_hash,
            public_key=group[0].address,
            sponge_type=sponge_type,
        )

        if validate_group_signature:
            return None

        return (
            'Transaction {i} has invalid signature '
            '(using {fragments} fragments).'.format(
                fragments=len(group),
                i=group[0].current_index,
            )
        )

Validates the signature fragments for a group of transactions using the specified sponge type .


def _traverse_bundle(self, txn_hash, target_bundle_hash=None):
        # type: (TransactionHash, Optional[BundleHash]) -> List[Transaction]
        """
        Recursively traverse the Tangle, collecting transactions until
        we hit a new bundle.

        This method is (usually) faster than ``findTransactions``, and
        it ensures we don't collect transactions from replayed bundles.
        """
        trytes = (
            GetTrytesCommand(self.adapter)(hashes=[txn_hash])['trytes']
        )  # type: List[TryteString]

        if not trytes:
            raise with_context(
                exc=BadApiResponse(
                    'Bundle transactions not visible '
                    '(``exc.context`` has more info).',
                ),

                context={
                    'transaction_hash': txn_hash,
                    'target_bundle_hash': target_bundle_hash,
                },
            )

        transaction = Transaction.from_tryte_string(trytes[0])

        if (not target_bundle_hash) and transaction.current_index:
            raise with_context(
                exc=BadApiResponse(
                    '``_traverse_bundle`` started with a non-tail transaction '
                    '(``exc.context`` has more info).',
                ),

                context={
                    'transaction_object': transaction,
                    'target_bundle_hash': target_bundle_hash,
                },
            )

        if target_bundle_hash:
            if target_bundle_hash != transaction.bundle_hash:
                # We've hit a different bundle; we can stop now.
                return []
        else:
            target_bundle_hash = transaction.bundle_hash

        if transaction.current_index == transaction.last_index == 0:
            # Bundle only has one transaction.
            return [transaction]

        # Recursively follow the trunk transaction, to fetch the next
        # transaction in the bundle.
        return [transaction] + self._traverse_bundle(
            txn_hash=transaction.trunk_transaction_hash,
            target_bundle_hash=target_bundle_hash
        )

Recursively traverse the Tangle collecting transactions until we hit a new bundle .


def _start_repl(api):
        # type: (Iota) -> None
        """
        Starts the REPL.
        """
        banner = (
            'IOTA API client for {uri} ({testnet}) '
            'initialized as variable `api`.\n'
            'Type `help(api)` for list of API commands.'.format(
                testnet='testnet' if api.testnet else 'mainnet',
                uri=api.adapter.get_uri(),
            )
        )

        scope_vars = {'api': api}

        try:
            # noinspection PyUnresolvedReferences
            import IPython
        except ImportError:
            # IPython not available; use regular Python REPL.
            from code import InteractiveConsole
            InteractiveConsole(locals=scope_vars).interact(banner, '')
        else:
            print(banner)
            IPython.start_ipython(argv=[], user_ns=scope_vars)


def random(cls, length=Hash.LEN):
        """
        Generates a random seed using a CSPRNG.

        :param length:
            Length of seed, in trytes.

            For maximum security, this should always be set to 81, but
            you can change it if you're 110% sure you know what you're
            doing.

            See https://iota.stackexchange.com/q/249 for more info.
        """
        return super(Seed, cls).random(length)

def random ( cls , length = Hash . LEN ) : return super ( Seed , cls ) . random ( length )

Generates a random seed using a CSPRNG .


def get_digest(self):
        # type: () -> Digest
        """
        Generates the digest used to do the actual signing.

        Signing keys can have variable length and tend to be quite long,
        which makes them not-well-suited for use in crypto algorithms.

        The digest is essentially the result of running the signing key
        through a PBKDF, yielding a constant-length hash that can be
        used for crypto.
        """
        hashes_per_fragment = FRAGMENT_LENGTH // Hash.LEN

        key_fragments = self.iter_chunks(FRAGMENT_LENGTH)

        # The digest will contain one hash per key fragment.
        digest = [0] * HASH_LENGTH * len(key_fragments)

        # Iterate over each fragment in the key.
        for i, fragment in enumerate(key_fragments):
            fragment_trits = fragment.as_trits()

            key_fragment = [0] * FRAGMENT_LENGTH
            hash_trits = []

            # Within each fragment, iterate over one hash at a time.
            for j in range(hashes_per_fragment):
                hash_start = j * HASH_LENGTH
                hash_end = hash_start + HASH_LENGTH
                hash_trits = fragment_trits[hash_start:hash_end]

                for k in range(26):
                    sponge = Kerl()
                    sponge.absorb(hash_trits)
                    sponge.squeeze(hash_trits)

                key_fragment[hash_start:hash_end] = hash_trits

            # After processing all of the hashes in the fragment,
            # generate a final hash and append it to the digest.
            #
            # Note that we will do this once per fragment in the key, so
            # the longer the key is, the longer the digest will be.
            sponge = Kerl()
            sponge.absorb(key_fragment)
            sponge.squeeze(hash_trits)

            fragment_hash_start = i * HASH_LENGTH
            fragment_hash_end = fragment_hash_start + HASH_LENGTH

            digest[fragment_hash_start:fragment_hash_end] = hash_trits

        return Digest(TryteString.from_trits(digest), self.key_index)

def get_digest ( self ) : hashes_per_fragment = FRAGMENT_LENGTH // Hash . LEN key_fragments = self . iter_chunks ( FRAGMENT_LENGTH ) digest = [ 0 ] * HASH_LENGTH * len ( key_fragments ) for i , fragment in enumerate ( key_fragments ) : fragment_trits = fragment . as_trits ( ) key_fragment = [ 0 ] * FRAGMENT_LENGTH hash_trits = [ ] for j in range ( hashes_per_fragment ) : hash_start = j * HASH_LENGTH hash_end = hash_start + HASH_LENGTH hash_trits = fragment_trits [ hash_start : hash_end ] for k in range ( 26 ) : sponge = Kerl ( ) sponge . absorb ( hash_trits ) sponge . squeeze ( hash_trits ) key_fragment [ hash_start : hash_end ] = hash_trits sponge = Kerl ( ) sponge . absorb ( key_fragment ) sponge . squeeze ( hash_trits ) fragment_hash_start = i * HASH_LENGTH fragment_hash_end = fragment_hash_start + HASH_LENGTH digest [ fragment_hash_start : fragment_hash_end ] = hash_trits return Digest ( TryteString . from_trits ( digest ) , self . key_index )

Generates the digest used to do the actual signing .


def sign_input_transactions(self, bundle, start_index):
        # type: (Bundle, int) -> None
        """
        Signs the inputs starting at the specified index.

        :param bundle:
            The bundle that contains the input transactions to sign.

        :param start_index:
            The index of the first input transaction.

            If necessary, the resulting signature will be split across
            subsequent transactions automatically.
        """

        if not bundle.hash:
            raise with_context(
                exc=ValueError('Cannot sign inputs without a bundle hash!'),

                context={
                    'bundle': bundle,
                    'key_index': self.key_index,
                    'start_index': start_index,
                },
            )

        from iota.crypto.signing import SignatureFragmentGenerator
        signature_fragment_generator = (
            SignatureFragmentGenerator(self, bundle.hash)
        )

        # We can only fit one signature fragment into each transaction,
        # so we have to split the entire signature.
        for j in range(self.security_level):
            # Do lots of validation before we attempt to sign the
            # transaction, and attach lots of context info to any
            # exception.
            #
            # This method is likely to be invoked at a very low level in
            # the application, so if anything goes wrong, we want to
            # make sure it's as easy to troubleshoot as possible!
            try:
                txn = bundle[start_index + j]
            except IndexError as e:
                raise with_context(
                    exc=e,

                    context={
                        'bundle': bundle,
                        'key_index': self.key_index,
                        'current_index': start_index + j,
                    },
                )

            # Only inputs can be signed.
            if txn.value > 0:
                raise with_context(
                    exc=ValueError(
                        'Attempting to sign non-input transaction #{i} '
                        '(value={value}).'.format(
                            i=txn.current_index,
                            value=txn.value,
                        ),
                    ),

                    context={
                        'bundle': bundle,
                        'key_index': self.key_index,
                        'start_index': start_index,
                    },
                )

            if txn.signature_message_fragment:
                raise with_context(
                    exc=ValueError(
                        'Attempting to sign input transaction #{i}, '
                        'but it has a non-empty fragment '
                        '(is it already signed?).'.format(
                            i=txn.current_index,
                        ),
                    ),

                    context={
                        'bundle': bundle,
                        'key_index': self.key_index,
                        'start_index': start_index,
                    },
                )

            txn.signature_message_fragment = next(signature_fragment_generator)

Signs the inputs starting at the specified index .


def _repr_pretty_(self, p, cycle):
        """
        Makes JSON-serializable objects play nice with IPython's default
        pretty-printer.

        Sadly, :py:func:`pprint.pprint` does not have a similar
        mechanism.

        References:

        - http://ipython.readthedocs.io/en/stable/api/generated/IPython.lib.pretty.html
        - :py:meth:`IPython.lib.pretty.RepresentationPrinter.pretty`
        - :py:func:`pprint._safe_repr`
        """
        class_name = type(self).__name__

        if cycle:
            p.text('{cls}(...)'.format(
                cls=class_name,
            ))
        else:
            with p.group(
                    len(class_name) + 1,
                    '{cls}('.format(cls=class_name),
                    ')',
            ):
                prepared = self.as_json_compatible()

                if isinstance(prepared, Mapping):
                    p.text('**')
                elif isinstance(prepared, Iterable):
                    p.text('*')

                p.pretty(prepared)

def _repr_pretty_ ( self , p , cycle ) : class_name = type ( self ) . __name__ if cycle : p . text ( '{cls}(...)' . format ( cls = class_name , ) ) else : with p . group ( len ( class_name ) + 1 , '{cls}(' . format ( cls = class_name ) , ')' , ) : prepared = self . as_json_compatible ( ) if isinstance ( prepared , Mapping ) : p . text ( '**' ) elif isinstance ( prepared , Iterable ) : p . text ( '*' ) p . pretty ( prepared )

Makes JSON - serializable objects play nice with IPython s default pretty - printer .


def absorb(self, trits, offset=0, length=None):
        # type: (MutableSequence[int], int, Optional[int]) -> None
        """
        Absorb trits into the sponge from a buffer.

        :param trits:
            Buffer that contains the trits to absorb.

        :param offset:
            Starting offset in ``trits``.

        :param length:
            Number of trits to absorb.  Defaults to ``len(trits)``.
        """
        # Pad input if necessary, so that it can be divided evenly into
        # hashes.
        # Note that this operation creates a COPY of ``trits``; the
        # incoming buffer is not modified!
        pad = ((len(trits) % TRIT_HASH_LENGTH) or TRIT_HASH_LENGTH)
        trits += [0] * (TRIT_HASH_LENGTH - pad)

        if length is None:
            length = len(trits)

        if length < 1:
            raise with_context(
                exc=ValueError('Invalid length passed to ``absorb``.'),

                context={
                    'trits': trits,
                    'offset': offset,
                    'length': length,
                },
            )

        while offset < length:
            stop = min(offset + TRIT_HASH_LENGTH, length)

            # If we're copying over a full chunk, zero last trit.
            if stop - offset == TRIT_HASH_LENGTH:
                trits[stop - 1] = 0

            signed_nums = conv.convertToBytes(trits[offset:stop])

            # Convert signed bytes into their equivalent unsigned
            # representation, in order to use Python's built-in bytes
            # type.
            unsigned_bytes = bytearray(
                conv.convert_sign(b) for b in signed_nums
            )

            self.k.update(unsigned_bytes)

            offset += TRIT_HASH_LENGTH

def
absorb
(
self
,
trits
,
offset
=
0
,
length
=
)
:
pad
=
(
(
len
(
trits
)
%
TRIT_HASH_LENGTH
)
or
TRIT_HASH_LENGTH
)
trits
+=
[
0
]
*
(
TRIT_HASH_LENGTH
pad
)
if
length
is
:
length
=
len
(
trits
)
if
length
<
1
:
raise
with_context
(
exc
=
ValueError
(
'Invalid length passed to ``absorb``.'
)
,
context
=
{
'trits'
:
trits
,
'offset'
:
offset
,
'length'
:
length
,
}
,
)
while
offset
<
length
:
stop
=
min
(
offset
+
TRIT_HASH_LENGTH
,
length
)
if
stop
offset
==
TRIT_HASH_LENGTH
:
trits
[
stop
1
]
=
0
signed_nums
=
conv
.
convertToBytes
(
trits
[
offset
:
stop
]
)
unsigned_bytes
=
bytearray
(
conv
.
convert_sign
(
b
)
for
b
in
signed_nums
)
self
.
k
.
update
(
unsigned_bytes
)
offset
+=
TRIT_HASH_LENGTH

Absorb trits into the sponge from a buffer .


def squeeze(self, trits, offset=0, length=None):
        # type: (MutableSequence[int], int, Optional[int]) -> None
        """
        Squeeze trits from the sponge into a buffer.

        :param trits:
            Buffer that will hold the squeezed trits.

            IMPORTANT:  If ``trits`` is too small, it will be extended!

        :param offset:
            Starting offset in ``trits``.

        :param length:
            Number of trits to squeeze from the sponge.

            If not specified, defaults to :py:data:`TRIT_HASH_LENGTH`
            (i.e., by default, we will try to squeeze exactly 1 hash).
        """
        # Pad input if necessary, so that it can be divided evenly into
        # hashes.
        pad = ((len(trits) % TRIT_HASH_LENGTH) or TRIT_HASH_LENGTH)
        trits += [0] * (TRIT_HASH_LENGTH - pad)

        if length is None:
            # By default, we will try to squeeze one hash.
            # Note that this is different than ``absorb``.
            length = len(trits) or TRIT_HASH_LENGTH

        if length < 1:
            raise with_context(
                exc=ValueError('Invalid length passed to ``squeeze``.'),

                context={
                    'trits': trits,
                    'offset': offset,
                    'length': length,
                },
            )

        while offset < length:
            unsigned_hash = self.k.digest()

            if PY2:
                unsigned_hash = map(ord, unsigned_hash)  # type: ignore

            signed_hash = [conv.convert_sign(b) for b in unsigned_hash]

            trits_from_hash = conv.convertToTrits(signed_hash)
            trits_from_hash[TRIT_HASH_LENGTH - 1] = 0

            stop = min(TRIT_HASH_LENGTH, length - offset)
            trits[offset:offset + stop] = trits_from_hash[0:stop]

            flipped_bytes = bytearray(
                conv.convert_sign(~b) for b in unsigned_hash
            )

            # Reset internal state before feeding back in.
            self.reset()
            self.k.update(flipped_bytes)

            offset += TRIT_HASH_LENGTH

def
squeeze
(
self
,
trits
,
offset
=
0
,
length
=
)
:
pad
=
(
(
len
(
trits
)
%
TRIT_HASH_LENGTH
)
or
TRIT_HASH_LENGTH
)
trits
+=
[
0
]
*
(
TRIT_HASH_LENGTH
pad
)
if
length
is
:
length
=
len
(
trits
)
or
TRIT_HASH_LENGTH
if
length
<
1
:
raise
with_context
(
exc
=
ValueError
(
'Invalid length passed to ``squeeze``.'
)
,
context
=
{
'trits'
:
trits
,
'offset'
:
offset
,
'length'
:
length
,
}
,
)
while
offset
<
length
:
unsigned_hash
=
self
.
k
.
digest
(
)
if
PY2
:
unsigned_hash
=
map
(
ord
,
unsigned_hash
)
signed_hash
=
[
conv
.
convert_sign
(
b
)
for
b
in
unsigned_hash
]
trits_from_hash
=
conv
.
convertToTrits
(
signed_hash
)
trits_from_hash
[
TRIT_HASH_LENGTH
1
]
=
0
stop
=
min
(
TRIT_HASH_LENGTH
,
length
offset
)
trits
[
offset
:
offset
+
stop
]
=
trits_from_hash
[
0
:
stop
]
flipped_bytes
=
bytearray
(
conv
.
convert_sign
(
~
b
)
for
b
in
unsigned_hash
)
self
.
reset
(
)
self
.
k
.
update
(
flipped_bytes
)
offset
+=
TRIT_HASH_LENGTH

Squeeze trits from the sponge into a buffer .


def with_context(exc, context):
    # type: (Exception, dict) -> Exception
    """
    Attaches a ``context`` value to an Exception.

    Before:

    .. code-block:: python

        exc = Exception('Frog blast the vent core!')
        exc.context = { ... }
        raise exc

    After:

    .. code-block:: python

        raise with_context(
            exc=Exception('Frog blast the vent core!'),
            context={ ... },
        )
    """
    if not hasattr(exc, 'context'):
        exc.context = {}

    exc.context.update(context)
    return exc

def with_context ( exc , context ) : if not hasattr ( exc , 'context' ) : exc . context = { } exc . context . update ( context ) return exc

Attaches a context value to an Exception .


def SecurityLevel():
    """
    Generates a filter chain for validating a security level.
    """
    return (
            f.Type(int) |
            f.Min(1) |
            f.Max(3) |
            f.Optional(default=AddressGenerator.DEFAULT_SECURITY_LEVEL)
    )

def SecurityLevel ( ) : return ( f . Type ( int ) | f . Min ( 1 ) | f . Max ( 3 ) | f . Optional ( default = AddressGenerator . DEFAULT_SECURITY_LEVEL ) )

Generates a filter chain for validating a security level .


def increment_legacy_tag(self):
        """
        Increments the transaction's legacy tag, used to fix insecure
        bundle hashes when finalizing a bundle.

        References:

        - https://github.com/iotaledger/iota.lib.py/issues/84
        """
        self._legacy_tag = (
            Tag.from_trits(add_trits(self.legacy_tag.as_trits(), [1]))
        )

def increment_legacy_tag ( self ) : self . _legacy_tag = ( Tag . from_trits ( add_trits ( self . legacy_tag . as_trits ( ) , [ 1 ] ) ) )

Increments the transaction s legacy tag used to fix insecure bundle hashes when finalizing a bundle .


def tag(self):
        # type: () -> Tag
        """
        Determines the most relevant tag for the bundle.
        """
        for txn in reversed(self):  # type: ProposedTransaction
            if txn.tag:
                return txn.tag

        return Tag(b'')

def tag ( self ) : for txn in reversed ( self ) : if txn . tag : return txn . tag return Tag ( b'' )

Determines the most relevant tag for the bundle .


def add_transaction(self, transaction):
        # type: (ProposedTransaction) -> None
        """
        Adds a transaction to the bundle.

        If the transaction message is too long, it will be split
        automatically into multiple transactions.
        """
        if self.hash:
            raise RuntimeError('Bundle is already finalized.')

        if transaction.value < 0:
            raise ValueError('Use ``add_inputs`` to add inputs to the bundle.')

        self._transactions.append(ProposedTransaction(
            address=transaction.address,
            value=transaction.value,
            tag=transaction.tag,
            message=transaction.message[:Fragment.LEN],
            timestamp=transaction.timestamp,
        ))

        # If the message is too long to fit in a single transactions,
        # it must be split up into multiple transactions so that it will
        # fit.
        fragment = transaction.message[Fragment.LEN:]
        while fragment:
            self._transactions.append(ProposedTransaction(
                address=transaction.address,
                value=0,
                tag=transaction.tag,
                message=fragment[:Fragment.LEN],
                timestamp=transaction.timestamp,
            ))

            fragment = fragment[Fragment.LEN:]


def finalize(self):
        # type: () -> None
        """
        Finalizes the bundle, preparing it to be attached to the Tangle.
        """
        if self.hash:
            raise RuntimeError('Bundle is already finalized.')

        if not self:
            raise ValueError('Bundle has no transactions.')

        # Quick validation.
        balance = self.balance

        if balance < 0:
            if self.change_address:
                self.add_transaction(ProposedTransaction(
                    address=self.change_address,
                    value=-balance,
                    tag=self.tag,
                ))
            else:
                raise ValueError(
                    'Bundle has unspent inputs (balance: {balance}); '
                    'use ``send_unspent_inputs_to`` to create '
                    'change transaction.'.format(
                        balance=balance,
                    ),
                )
        elif balance > 0:
            raise ValueError(
                'Inputs are insufficient to cover bundle spend '
                '(balance: {balance}).'.format(
                    balance=balance,
                ),
            )

        # Generate bundle hash.
        while True:
            sponge = Kerl()
            last_index = len(self) - 1

            for i, txn in enumerate(self):
                txn.current_index = i
                txn.last_index = last_index

                sponge.absorb(txn.get_signature_validation_trytes().as_trits())

            bundle_hash_trits = [0] * HASH_LENGTH
            sponge.squeeze(bundle_hash_trits)

            bundle_hash = BundleHash.from_trits(bundle_hash_trits)

            # Check that we generated a secure bundle hash.
            # https://github.com/iotaledger/iota.lib.py/issues/84
            if any(13 in part for part in normalize(bundle_hash)):
                # Increment the legacy tag and try again.
                tail_transaction = (
                    self.tail_transaction
                )  # type: ProposedTransaction
                tail_transaction.increment_legacy_tag()
            else:
                break

        # Copy bundle hash to individual transactions.
        for txn in self:
            txn.bundle_hash = bundle_hash

            # Initialize signature/message fragment.
            txn.signature_message_fragment = Fragment(txn.message or b'')

Finalizes the bundle preparing it to be attached to the Tangle .


def sign_inputs(self, key_generator):
        # type: (KeyGenerator) -> None
        """
        Sign inputs in a finalized bundle.
        """
        if not self.hash:
            raise RuntimeError('Cannot sign inputs until bundle is finalized.')

        # Use a counter for the loop so that we can skip ahead as we go.
        i = 0
        while i < len(self):
            txn = self[i]

            if txn.value < 0:
                # In order to sign the input, we need to know the index
                # of the private key used to generate it.
                if txn.address.key_index is None:
                    raise with_context(
                        exc=ValueError(
                            'Unable to sign input {input}; '
                            '``key_index`` is None '
                            '(``exc.context`` has more info).'.format(
                                input=txn.address,
                            ),
                        ),

                        context={
                            'transaction': txn,
                        },
                    )

                if txn.address.security_level is None:
                    raise with_context(
                        exc=ValueError(
                            'Unable to sign input {input}; '
                            '``security_level`` is None '
                            '(``exc.context`` has more info).'.format(
                                input=txn.address,
                            ),
                        ),

                        context={
                            'transaction': txn,
                        },
                    )

                self.sign_input_at(i, key_generator.get_key_for(txn.address))

                i += txn.address.security_level
            else:
                # No signature needed (nor even possible, in some
                # cases); skip this transaction.
                i += 1


def sign_input_at(self, start_index, private_key):
        # type: (int, PrivateKey) -> None
        """
        Signs the input at the specified index.

        :param start_index:
            The index of the first input transaction.

            If necessary, the resulting signature will be split across
            multiple transactions automatically (i.e., if an input has
            ``security_level=2``, you still only need to call
            :py:meth:`sign_input_at` once).

        :param private_key:
            The private key that will be used to generate the signature.

            .. important::
                Be sure that the private key was generated using the
                correct seed, or the resulting signature will be
                invalid!
        """
        if not self.hash:
            raise RuntimeError('Cannot sign inputs until bundle is finalized.')

        private_key.sign_input_transactions(self, start_index)

Signs the input at the specified index .


def _create_input_transactions(self, addy):
        # type: (Address) -> None
        """
        Creates transactions for the specified input address.
        """
        self._transactions.append(ProposedTransaction(
            address=addy,
            tag=self.tag,

            # Spend the entire address balance; if necessary, we will
            # add a change transaction to the bundle.
            value=-addy.balance,
        ))

        # Signatures require additional transactions to store, due to
        # transaction length limit.
        # Subtract 1 to account for the transaction we just added.
        for _ in range(addy.security_level - 1):
            self._transactions.append(ProposedTransaction(
                address=addy,
                tag=self.tag,

                # Note zero value; this is a meta transaction.
                value=0,
            ))

def _create_input_transactions ( self , addy ) : self . _transactions . append ( ProposedTransaction ( address = addy , tag = self . tag , value = - addy . balance , ) ) for _ in range ( addy . security_level - 1 ) : self . _transactions . append ( ProposedTransaction ( address = addy , tag = self . tag , value = 0 , ) )

Creates transactions for the specified input address .


def convert_value_to_standard_unit(value, symbol='i'):
    # type: (Text, Text) -> float
    """
    Converts between any two standard units of iota.

    :param value:
        Value (affixed) to convert. For example: '1.618 Mi'.

    :param symbol:
        Unit symbol of iota to convert to. For example: 'Gi'.

    :return:
        Float as units of given symbol to convert to.
    """
    try:
        # Get input value
        value_tuple = value.split()
        amount = float(value_tuple[0])
    except (ValueError, IndexError, AttributeError):
        raise with_context(
            ValueError('Value to convert is not valid.'),

            context={
                'value': value,
            },
        )

    try:
        # Set unit symbols and find factor/multiplier.
        unit_symbol_from = value_tuple[1]
        unit_factor_from = float(STANDARD_UNITS[unit_symbol_from])
        unit_factor_to = float(STANDARD_UNITS[symbol])
    except (KeyError, IndexError):
        # Invalid symbol or no factor
        raise with_context(
            ValueError('Invalid IOTA unit.'),

            context={
                'value': value,
                'symbol': symbol,
            },
        )

    return amount * (unit_factor_from / unit_factor_to)

def
convert_value_to_standard_unit
(
value
,
symbol
=
'i'
)
:
try
:
value_tuple
=
value
.
split
(
)
amount
=
float
(
value_tuple
[
0
]
)
except
(
ValueError
,
IndexError
,
AttributeError
)
:
raise
with_context
(
ValueError
(
'Value to convert is not valid.'
)
,
context
=
{
'value'
:
value
,
}
,
)
try
:
unit_symbol_from
=
value_tuple
[
1
]
unit_factor_from
=
float
(
STANDARD_UNITS
[
unit_symbol_from
]
)
unit_factor_to
=
float
(
STANDARD_UNITS
[
symbol
]
)
except
(
KeyError
,
IndexError
)
:
raise
with_context
(
ValueError
(
'Invalid IOTA unit.'
)
,
context
=
{
'value'
:
value
,
'symbol'
:
symbol
,
}
,
)
return
amount
*
(
unit_factor_from
/
unit_factor_to
)

Converts between any two standard units of iota .


def decompress_G1(z: G1Compressed) -> G1Uncompressed:
    """
    Recovers x and y coordinates from the compressed point.
    """
    # b_flag == 1 indicates the infinity point
    b_flag = (z % POW_2_383) // POW_2_382
    if b_flag == 1:
        return Z1
    x = z % POW_2_381

    # Try solving y coordinate from the equation Y^2 = X^3 + b
    # using quadratic residue
    y = pow((x**3 + b.n) % q, (q + 1) // 4, q)

    if pow(y, 2, q) != (x**3 + b.n) % q:
        raise ValueError(
            "The given point is not on G1: y**2 = x**3 + b"
        )
    # Choose the y whose leftmost bit is equal to the a_flag
    a_flag = (z % POW_2_382) // POW_2_381
    if (y * 2) // q != a_flag:
        y = q - y
    return (FQ(x), FQ(y), FQ(1))

Recovers x and y coordinates from the compressed point .


def prime_field_inv(a: int, n: int) -> int:
    """
    Extended euclidean algorithm to find modular inverses for integers
    """
    if a == 0:
        return 0
    lm, hm = 1, 0
    low, high = a % n, n
    while low > 1:
        r = high // low
        nm, new = hm - lm * r, high - low * r
        lm, low, hm, high = nm, new, lm, low
    return lm % n

def prime_field_inv ( a : int , n : int ) -> int : if a == 0 : return 0 lm , hm = 1 , 0 low , high = a % n , n while low > 1 : r = high // low nm , new = hm - lm * r , high - low * r lm , low , hm , high = nm , new , lm , low return lm % n


def from_json_file(cls, filename):
        """
        Load a lexicon from a JSON file.

        Args:
            filename (str): The path to a JSON dump.
        """
        with open(filename, 'r') as fp:
            return cls(json.load(fp))

def from_json_file ( cls , filename ) : with open ( filename , 'r' ) as fp : return cls ( json . load ( fp ) )


def find_word_groups(self, text, category, proximity=2):
        """
        Given a string and a category, finds and combines words into
        groups based on their proximity.

        Args:
            text (str): Some text.
            tokens (list): A list of regex strings.

        Returns:
            list. The combined strings it found.

        Example:
            COLOURS = [r"red(?:dish)?", r"grey(?:ish)?", r"green(?:ish)?"]
            s = 'GREYISH-GREEN limestone with RED or GREY sandstone.'
            find_word_groups(s, COLOURS) --> ['greyish green', 'red', 'grey']
        """
        f = re.IGNORECASE
        words = getattr(self, category)
        regex = re.compile(r'(\b' + r'\b|\b'.join(words) + r'\b)', flags=f)
        candidates = regex.finditer(text)

        starts, ends = [], []
        groups = []

        for item in candidates:
            starts.append(item.span()[0])
            ends.append(item.span()[1])
            groups.append(item.group().lower())

        new_starts = []  # As a check only.
        new_groups = []  # This is what I want.

        skip = False
        for i, g in enumerate(groups):
            if skip:
                skip = False
                continue
            if (i < len(groups)-1) and (starts[i+1]-ends[i] <= proximity):
                if g[-1] == '-':
                    sep = ''  # Don't insert spaces after hyphens.
                else:
                    sep = ' '
                new_groups.append(g + sep + groups[i+1])
                new_starts.append(starts[i])
                skip = True
            else:
                if g not in new_groups:
                    new_groups.append(g)
                    new_starts.append(starts[i])
                skip = False

        return new_groups

def
find_word_groups
(
self
,
text
,
category
,
proximity
=
2
)
:
f
=
re
.
IGNORECASE
words
=
getattr
(
self
,
category
)
regex
=
re
.
compile
(
r'(\b'
+
r'\b|\b'
.
join
(
words
)
+
r'\b)'
,
flags
=
f
)
candidates
=
regex
.
finditer
(
text
)
starts
,
ends
=
[
]
,
[
]
groups
=
[
]
for
item
in
candidates
:
starts
.
append
(
item
.
span
(
)
[
0
]
)
ends
.
append
(
item
.
span
(
)
[
1
]
)
groups
.
append
(
item
.
group
(
)
.
lower
(
)
)
new_starts
=
[
]
new_groups
=
[
]
skip
=
False
for
i
,
g
in
enumerate
(
groups
)
:
if
skip
:
skip
=
False
continue
if
(
i
<
len
(
groups
)
1
)
and
(
starts
[
i
+
1
]
ends
[
i
]
<=
proximity
)
:
if
g
[
1
]
==
'-'
:
sep
=
''
else
:
sep
=
' '
new_groups
.
append
(
g
+
sep
+
groups
[
i
+
1
]
)
new_starts
.
append
(
starts
[
i
]
)
skip
=
True
else
:
if
g
not
in
new_groups
:
new_groups
.
append
(
g
)
new_starts
.
append
(
starts
[
i
]
)
skip
=
False
return
new_groups

Given a string and a category finds and combines words into groups based on their proximity .


def find_synonym(self, word):
        """
        Given a string and a dict of synonyms, returns the 'preferred'
        word. Case insensitive.

        Args:
            word (str): A word.

        Returns:
            str: The preferred word, or the input word if not found.

        Example:
            >>> syn = {'snake': ['python', 'adder']}
            >>> find_synonym('adder', syn)
            'snake'
            >>> find_synonym('rattler', syn)
            'rattler'

        TODO:
            Make it handle case, returning the same case it received.
        """
        if word and self.synonyms:
            # Make the reverse look-up table.
            reverse_lookup = {}
            for k, v in self.synonyms.items():
                for i in v:
                    reverse_lookup[i.lower()] = k.lower()

            # Now check words against this table.
            if word.lower() in reverse_lookup:
                return reverse_lookup[word.lower()]

        return word

def find_synonym ( self , word ) : if word and self . synonyms : reverse_lookup = { } for k , v in self . synonyms . items ( ) : for i in v : reverse_lookup [ i . lower ( ) ] = k . lower ( ) if word . lower ( ) in reverse_lookup : return reverse_lookup [ word . lower ( ) ] return word

Given a string and a dict of synonyms returns the preferred word . Case insensitive .


def expand_abbreviations(self, text):
        """
        Parse a piece of text and replace any abbreviations with their full
        word equivalents. Uses the lexicon.abbreviations dictionary to find
        abbreviations.

        Args:
            text (str): The text to parse.

        Returns:
            str: The text with abbreviations replaced.
        """
        if not self.abbreviations:
            raise LexiconError("No abbreviations in lexicon.")

        def chunks(data, SIZE=25):
            """
            Regex only supports 100 groups for munging callbacks. So we have to
            chunk the abbreviation dicitonary.
            """
            it = iter(data)
            for i in range(0, len(data), SIZE):
                yield {k: data[k] for k in islice(it, SIZE)}

        def cb(g):
            """Regex callback"""
            return self.abbreviations.get(g.group(0)) or g.group(0)

        # Special cases.

        # TODO: We should handle these with a special set of
        # replacements that are made before the others.
        text = re.sub(r'w/', r'wi', text)

        # Main loop.
        for subdict in chunks(self.abbreviations):
            regex = r'(\b' + r'\b)|(\b'.join(subdict.keys()) + r'\b)'
            text = re.sub(regex, cb, text)

        return text

def
expand_abbreviations
(
self
,
text
)
:
if
not
self
.
abbreviations
:
raise
LexiconError
(
"No abbreviations in lexicon."
)
def
chunks
(
data
,
SIZE
=
25
)
:
it
=
iter
(
data
)
for
i
in
range
(
0
,
len
(
data
)
,
SIZE
)
:
yield
{
k
:
data
[
k
]
for
k
in
islice
(
it
,
SIZE
)
}
def
cb
(
g
)
:
return
self
.
abbreviations
.
get
(
g
.
group
(
0
)
)
or
g
.
group
(
0
)
text
=
re
.
sub
(
r'w/'
,
r'wi'
,
text
)
for
subdict
in
chunks
(
self
.
abbreviations
)
:
regex
=
r'(\b'
+
r'\b)|(\b'
.
join
(
subdict
.
keys
(
)
)
+
r'\b)'
text
=
re
.
sub
(
regex
,
cb
,
text
)
return
text

Parse a piece of text and replace any abbreviations with their full word equivalents . Uses the lexicon . abbreviations dictionary to find abbreviations .


def split_description(self, text):
        """
        Split a description into parts, each of which can be turned into
        a single component.
        """
        # Protect some special sequences.
        t = re.sub(r'(\d) ?in\. ', r'\1 inch ', text)  # Protect.
        t = re.sub(r'(\d) ?ft\. ', r'\1 feet ', t)  # Protect.

        # Transform all part delimiters to first splitter.
        words = getattr(self, 'splitters')
        try:
            splitter = words[0].strip()
        except:
            splitter = 'with'
        t = re.sub(r'\,?\;?\.? ?((under)?(less than)? \d+%) (?=\w)', r' '+splitter+' \1 ', t)

        # Split.
        f = re.IGNORECASE
        pattern = re.compile(r'(?:' + r'|'.join(words) + r')', flags=f)
        parts = filter(None, pattern.split(t))

        return [i.strip() for i in parts]

Split a description into parts each of which can be turned into a single component .


def categories(self):
        """
        Lists the categories in the lexicon, except the
        optional categories.

        Returns:
            list: A list of strings of category names.
        """
        keys = [k for k in self.__dict__.keys() if k not in SPECIAL]
        return keys

def categories ( self ) : keys = [ k for k in self . __dict__ . keys ( ) if k not in SPECIAL ] return keys

Lists the categories in the lexicon except the optional categories .


def random(cls, component):
        """
        Returns a minimal Decor with a random colour.
        """
        colour = random.sample([i for i in range(256)], 3)
        return cls({'colour': colour, 'component': component, 'width': 1.0})

def random ( cls , component ) : colour = random . sample ( [ i for i in range ( 256 ) ] , 3 ) return cls ( { 'colour' : colour , 'component' : component , 'width' : 1.0 } )

Returns a minimal Decor with a random colour .


def plot(self, fmt=None, fig=None, ax=None):
        """
        Make a simple plot of the Decor.

        Args:
            fmt (str): A Python format string for the component summaries.
            fig (Pyplot figure): A figure, optional. Use either fig or ax, not
                both.
            ax (Pyplot axis): An axis, optional. Use either fig or ax, not
                both.

        Returns:
            fig or ax or None. If you pass in an ax, you get it back. If you pass
                in a fig, you get it. If you pass nothing, the function creates a
                plot object as a side-effect.
        """

        u = 4     # aspect ratio of decor plot
        v = 0.25  # ratio of decor tile width

        r = None

        if (fig is None) and (ax is None):
            fig = plt.figure(figsize=(u, 1))
        else:
            r = fig

        if ax is None:
            ax = fig.add_axes([0.1*v, 0.1, 0.8*v, 0.8])
        else:
            r = ax

        rect1 = patches.Rectangle((0, 0),
                                  u*v, u*v,
                                  color=self.colour,
                                  lw=1,
                                  hatch=self.hatch,
                                  ec='k')
        ax.add_patch(rect1)
        ax.text(1.0+0.1*v*u, u*v*0.5,
                self.component.summary(fmt=fmt),
                fontsize=max(u, 15),
                verticalalignment='center',
                horizontalalignment='left')
        ax.set_xlim([0, u*v])
        ax.set_ylim([0, u*v])
        ax.get_xaxis().set_visible(False)
        ax.get_yaxis().set_visible(False)
        ax.invert_yaxis()

        return r

def plot ( self , fmt = None , fig = None , ax = None ) : u = 4 v = 0.25 r = None if ( fig is None ) and ( ax is None ) : fig = plt . figure ( figsize = ( u , 1 ) ) else : r = fig if ax is None : ax = fig . add_axes ( [ 0.1 * v , 0.1 , 0.8 * v , 0.8 ] ) else : r = ax rect1 = patches . Rectangle ( ( 0 , 0 ) , u * v , u * v , color = self . colour , lw = 1 , hatch = self . hatch , ec = 'k' ) ax . add_patch ( rect1 ) ax . text ( 1.0 + 0.1 * v * u , u * v * 0.5 , self . component . summary ( fmt = fmt ) , fontsize = max ( u , 15 ) , verticalalignment = 'center' , horizontalalignment = 'left' ) ax . set_xlim ( [ 0 , u * v ] ) ax . set_ylim ( [ 0 , u * v ] ) ax . get_xaxis ( ) . set_visible ( False ) ax . get_yaxis ( ) . set_visible ( False ) ax . invert_yaxis ( ) return r


def builtin(cls, name):
        """
        Generate a default legend.

        Args:
            name (str): The name of the legend you want. Not case sensitive.
                 'nsdoe': Nova Scotia Dept. of Energy
                 'canstrat': Canstrat
                 'nagmdm__6_2': USGS N. Am. Geol. Map Data Model 6.2
                 'nagmdm__6_1': USGS N. Am. Geol. Map Data Model 6.1
                 'nagmdm__4_3': USGS N. Am. Geol. Map Data Model 4.3
                 'sgmc': USGS State Geologic Map Compilation

            Default 'nagmdm__6_2'.

        Returns:
            Legend: The legend stored in `defaults.py`.
        """
        names = {
                 'nsdoe': LEGEND__NSDOE,
                 'canstrat': LEGEND__Canstrat,
                 'nagmdm__6_2': LEGEND__NAGMDM__6_2,
                 'nagmdm__6_1': LEGEND__NAGMDM__6_1,
                 'nagmdm__4_3': LEGEND__NAGMDM__4_3,
                 'sgmc': LEGEND__SGMC,
                 }
        return cls.from_csv(text=names[name.lower()])

def builtin ( cls , name ) : names = { 'nsdoe' : LEGEND__NSDOE , 'canstrat' : LEGEND__Canstrat , 'nagmdm__6_2' : LEGEND__NAGMDM__6_2 , 'nagmdm__6_1' : LEGEND__NAGMDM__6_1 , 'nagmdm__4_3' : LEGEND__NAGMDM__4_3 , 'sgmc' : LEGEND__SGMC , } return cls . from_csv ( text = names [ name . lower ( ) ] )


def builtin_timescale(cls, name):
        """
        Generate a default timescale legend. No arguments.

        Returns:
            Legend: The timescale stored in `defaults.py`.
        """
        names = {
                 'isc': TIMESCALE__ISC,
                 'usgs_isc': TIMESCALE__USGS_ISC,
                 'dnag': TIMESCALE__DNAG,
                 }
        return cls.from_csv(text=names[name.lower()])

def builtin_timescale ( cls , name ) : names = { 'isc' : TIMESCALE__ISC , 'usgs_isc' : TIMESCALE__USGS_ISC , 'dnag' : TIMESCALE__DNAG , } return cls . from_csv ( text = names [ name . lower ( ) ] )

Generate a default timescale legend . No arguments .


def random(cls, components, width=False, colour=None):
        """
        Generate a random legend for a given list of components.

        Args:
            components (list or Striplog): A list of components. If you pass
                a Striplog, it will use the primary components. If you pass a
                component on its own, you will get a random Decor.
            width (bool): Also generate widths for the components, based on the
                order in which they are encountered.
            colour (str): If you want to give the Decors all the same colour,
                provide a hex string.
        Returns:
            Legend or Decor: A legend (or Decor) with random colours.
        TODO:
            It might be convenient to have a partial method to generate an
            'empty' legend. Might be an easy way for someone to start with a
            template, since it'll have the components in it already.
        """
        try:  # Treating as a Striplog.
            list_of_Decors = [Decor.random(c)
                              for c
                              in [i[0] for i in components.unique if i[0]]
                              ]
        except:
            try:
                list_of_Decors = [Decor.random(c) for c in components.copy()]
            except:
                # It's a single component.
                list_of_Decors = [Decor.random(components)]

        if colour is not None:
            for d in list_of_Decors:
                d.colour = colour

        if width:
            for i, d in enumerate(list_of_Decors):
                d.width = i + 1

        return cls(list_of_Decors)

def random ( cls , components , width = False , colour = None ) : try : list_of_Decors = [ Decor . random ( c ) for c in [ i [ 0 ] for i in components . unique if i [ 0 ] ] ] except : try : list_of_Decors = [ Decor . random ( c ) for c in components . copy ( ) ] except : list_of_Decors = [ Decor . random ( components ) ] if colour is not None : for d in list_of_Decors : d . colour = colour if width : for i , d in enumerate ( list_of_Decors ) : d . width = i + 1 return cls ( list_of_Decors )

Generate a random legend for a given list of components .


def from_image(cls, filename, components,
                   ignore=None,
                   col_offset=0.1,
                   row_offset=2):
        """
        A slightly easier way to make legends from images.

        Args:
            filename (str)
            components (list)
            ignore (list): Colours to ignore, e.g. "#FFFFFF" to ignore white.
            col_offset (Number): If < 1, interpreted as proportion of way
                across the image. If > 1, interpreted as pixels from left.
            row_offset (int): Number of pixels to skip at the top of each
                interval.
        """
        if ignore is None:
            ignore = []

        rgb = utils.loglike_from_image(filename, offset=col_offset)
        loglike = np.array([utils.rgb_to_hex(t) for t in rgb])

        # Get the pixels and colour values at 'tops' (i.e. changes).
        _, hexes = utils.tops_from_loglike(loglike, offset=row_offset)

        # Reduce to unique colours.
        hexes_reduced = []
        for h in hexes:
            if h not in hexes_reduced:
                if h not in ignore:
                    hexes_reduced.append(h)

        list_of_Decors = []
        for i, c in enumerate(components):
            d = Decor({'colour': hexes_reduced[i], 'component': c})
            list_of_Decors.append(d)

        return cls(list_of_Decors)

def from_image ( cls , filename , components , ignore = None , col_offset = 0.1 , row_offset = 2 ) : if ignore is None : ignore = [ ] rgb = utils . loglike_from_image ( filename , offset = col_offset ) loglike = np . array ( [ utils . rgb_to_hex ( t ) for t in rgb ] ) _ , hexes = utils . tops_from_loglike ( loglike , offset = row_offset ) hexes_reduced = [ ] for h in hexes : if h not in hexes_reduced : if h not in ignore : hexes_reduced . append ( h ) list_of_Decors = [ ] for i , c in enumerate ( components ) : d = Decor ( { 'colour' : hexes_reduced [ i ] , 'component' : c } ) list_of_Decors . append ( d ) return cls ( list_of_Decors )

A slightly easier way to make legends from images .


def from_csv(cls, filename=None, text=None):
        """
        Read CSV text and generate a Legend.

        Args:
            string (str): The CSV string.

        In the first row, list the properties. Precede the properties of the
        component with 'comp ' or 'component '. For example:

        colour,  width, comp lithology, comp colour
        #FFFFFF, 0, ,
        #F7E9A6, 3, Sandstone, Grey
        #FF99CC, 2, Anhydrite,
        ... etc

        Note:
            To edit a legend, the easiest thing to do is probably this:

            - `legend.to_csv()`
            - Edit the legend, call it `new_legend`.
            - `legend = Legend.from_csv(text=new_legend)`
        """
        if (filename is None) and (text is None):
            raise LegendError("You must provide a filename or CSV text.")

        if (filename is not None):
            with open(filename, 'r') as f:
                text = f.read()

        try:
            f = StringIO(text)  # Python 3
        except TypeError:
            f = StringIO(unicode(text))  # Python 2

        r = csv.DictReader(f, skipinitialspace=True)
        list_of_Decors, components = [], []
        kind = 'component'
        for row in r:
            d, component = {}, {}
            for (k, v) in row.items():
                if (k in [None, '']):
                    continue
                if (v in [None, '']):
                    if k.lower() not in ['color', 'colour']:
                        continue
                if k[:4].lower() == 'comp':
                    prop = ' '.join(k.split()[1:])
                    if v.lower() == 'true':
                        component[prop] = True
                    elif v.lower() == 'false':
                        component[prop] = False
                    else:
                        try:
                            component[prop] = float(v)
                        except ValueError:
                            component[prop] = v.lower()

                elif k[:5].lower() == 'curve':
                    prop = ' '.join(k.split()[1:])
                    component[prop] = v.lower()
                    kind = 'curve'
                else:
                    try:
                        d[k] = float(v)
                    except ValueError:
                        d[k] = v.lower()

            this_component = Component(component)
            d[kind] = this_component

            # Check for duplicates and warn.
            if this_component in components:
                with warnings.catch_warnings():
                    warnings.simplefilter("always")
                    w = "This legend contains duplicate components."
                    warnings.warn(w)
            components.append(this_component)

            # Append to the master list and continue.
            list_of_Decors.append(Decor(d))

        return cls(list_of_Decors)


def to_csv(self):
        """
        Renders a legend as a CSV string.

        No arguments.

        Returns:
            str: The legend as a CSV.
        """
        # We can't delegate this to Decor because we need to know the superset
        # of all Decor properties. There may be lots of blanks.
        header = []
        component_header = []
        for row in self:
            for j in row.__dict__.keys():
                if j == '_colour':
                    j = 'colour'
                header.append(j)
            for k in row.component.__dict__.keys():
                component_header.append(k)
        header = set(header)
        component_header = set(component_header)
        header.remove('component')
        header_row = ''
        if 'colour' in header:
            header_row += 'colour,'
            header.remove('colour')
            has_colour = True
        for item in header:
            header_row += item + ','
        for item in component_header:
            header_row += 'component ' + item + ','

        # Now we have a header row! Phew.
        # Next we'll go back over the legend and collect everything.
        result = header_row.strip(',') + '\n'
        for row in self:
            if has_colour:
                result += row.__dict__.get('_colour', '') + ','
            for item in header:
                result += str(row.__dict__.get(item, '')) + ','
            for item in component_header:
                result += str(row.component.__dict__.get(item, '')) + ','
            result += '\n'

        return result

def
to_csv
(
self
)
:
header
=
[
]
component_header
=
[
]
for
row
in
self
:
for
j
in
row
.
__dict__
.
keys
(
)
:
if
j
==
'_colour'
:
j
=
'colour'
header
.
append
(
j
)
for
k
in
row
.
component
.
__dict__
.
keys
(
)
:
component_header
.
append
(
k
)
header
=
set
(
header
)
component_header
=
set
(
component_header
)
header
.
remove
(
'component'
)
header_row
=
''
if
'colour'
in
header
:
header_row
+=
'colour,'
header
.
remove
(
'colour'
)
has_colour
=
True
for
item
in
header
:
header_row
+=
item
+
','
for
item
in
component_header
:
header_row
+=
'component '
+
item
+
','
result
=
header_row
.
strip
(
','
)
+
'\n'
for
row
in
self
:
if
has_colour
:
result
+=
row
.
__dict__
.
get
(
'_colour'
,
''
)
+
','
for
item
in
header
:
result
+=
str
(
row
.
__dict__
.
get
(
item
,
''
)
)
+
','
for
item
in
component_header
:
result
+=
str
(
row
.
component
.
__dict__
.
get
(
item
,
''
)
)
+
','
result
+=
'\n'
return
result


def max_width(self):
        """
        The maximum width of all the Decors in the Legend. This is needed
        to scale a Legend or Striplog when plotting with widths turned on.
        """
        try:
            maximum = max([row.width for row in self.__list if row.width is not None])
            return maximum
        except:
            return 0

def max_width ( self ) : try : maximum = max ( [ row . width for row in self . __list if row . width is not None ] ) return maximum except : return 0

The maximum width of all the Decors in the Legend . This is needed to scale a Legend or Striplog when plotting with widths turned on .


def get_decor(self, c, match_only=None):
        """
        Get the decor for a component.

        Args:
           c (component): The component to look up.
           match_only (list of str): The component attributes to include in the
               comparison. Default: All of them.

        Returns:
           Decor. The matching Decor from the Legend, or None if not found.
        """
        if isinstance(c, Component):
            if c:
                if match_only:
                    # Filter the component only those attributes
                    c = Component({k: getattr(c, k, None) for k in match_only})
                for decor in self.__list:
                    try:
                        if c == decor.component:
                            return decor
                    except AttributeError:
                        continue
        else:
            for decor in self.__list:
                try:
                    if getattr(c, 'mnemonic').lower() == decor.curve.mnemonic:
                        return decor
                except AttributeError:
                    continue
        return Decor({'colour': '#eeeeee', 'component': Component()})

def get_decor ( self , c , match_only = None ) : if isinstance ( c , Component ) : if c : if match_only : c = Component ( { k : getattr ( c , k , None ) for k in match_only } ) for decor in self . __list : try : if c == decor . component : return decor except AttributeError : continue else : for decor in self . __list : try : if getattr ( c , 'mnemonic' ) . lower ( ) == decor . curve . mnemonic : return decor except AttributeError : continue return Decor ( { 'colour' : '#eeeeee' , 'component' : Component ( ) } )


def getattr(self, c, attr, default=None, match_only=None):
        """
        Get the attribute of a component.

        Args:
           c (component): The component to look up.
           attr (str): The attribute to get.
           default (str): What to return in the event of no match.
           match_only (list of str): The component attributes to include in the
               comparison. Default: All of them.

        Returns:
           obj. The specified attribute of the matching Decor in the Legend.
        """
        matching_decor = self.get_decor(c, match_only=match_only)

        try:
            return getattr(matching_decor, attr)
        except AttributeError:
            return default

def getattr ( self , c , attr , default = None , match_only = None ) : matching_decor = self . get_decor ( c , match_only = match_only ) try : return getattr ( matching_decor , attr ) except AttributeError : return default


def get_component(self, colour, tolerance=0, default=None):
        """
        Get the component corresponding to a display colour. This is for
        generating a Striplog object from a colour image of a striplog.

        Args:
           colour (str): The hex colour string to look up.
           tolerance (float): The colourspace distance within which to match.
           default (component or None): The component to return in the event
           of no match.

        Returns:
           component. The component best matching the provided colour.
        """
        if not (0 <= tolerance <= np.sqrt(195075)):
            raise LegendError('Tolerance must be between 0 and 441.67')

        for decor in self.__list:
            if colour.lower() == decor.colour:
                return decor.component

        # If we're here, we didn't find one yet.
        r1, g1, b1 = utils.hex_to_rgb(colour)

        # Start with a best match of black.
        best_match = '#000000'
        best_match_dist = np.sqrt(r1**2. + g1**2. + b1**2.)

        # Now compare to each colour in the legend.
        for decor in self.__list:
            r2, g2, b2 = decor.rgb
            distance = np.sqrt((r2-r1)**2. + (g2-g1)**2. + (b2-b1)**2.)
            if distance < best_match_dist:
                best_match = decor.component
                best_match_dist = distance
                best_match_colour = decor.colour

        if best_match_dist <= tolerance:
            return best_match
        else:
            with warnings.catch_warnings():
                warnings.simplefilter("always")
                w = "No match found for {0} ".format(colour.lower())
                w += "with tolerance of {0}. Best match is ".format(tolerance)
                w += "{0}, {1}".format(best_match.summary(), best_match_colour)
                w += ", d={0}".format(best_match_dist)
                warnings.warn(w)

            return default

Get the component corresponding to a display colour . This is for generating a Striplog object from a colour image of a striplog .


def plot(self, fmt=None):
        """
        Make a simple plot of the legend.

        Simply calls Decor.plot() on all of its members.

        TODO: Build a more attractive plot.
        """
        for d in self.__list:
            d.plot(fmt=fmt)

        return None

def plot ( self , fmt = None ) : for d in self . __list : d . plot ( fmt = fmt ) return None


def from_text(cls, text, lexicon, required=None, first_only=True):
        """
        Generate a Component from a text string, using a Lexicon.

        Args:
            text (str): The text string to parse.
            lexicon (Lexicon): The dictionary to use for the
                categories and lexemes.
            required (str): An attribute that we must have. If a required
                attribute is missing from the component, then None is returned.
            first_only (bool): Whether to only take the first
                match of a lexeme against the text string.

        Returns:
            Component: A Component object, or None if there was no
                must-have field.
        """
        component = lexicon.get_component(text, first_only=first_only)
        if required and (required not in component):
            return None
        else:
            return cls(component)

def from_text ( cls , text , lexicon , required = None , first_only = True ) : component = lexicon . get_component ( text , first_only = first_only ) if required and ( required not in component ) : return None else : return cls ( component )

Generate a Component from a text string using a Lexicon .


def summary(self, fmt=None, initial=True, default=''):
        """
        Given a format string, return a summary description of a component.

        Args:
            component (dict): A component dictionary.
            fmt (str): Describes the format with a string. If no format is
                given, you will just get a list of attributes. If you give the
                empty string (''), you'll get `default` back. By default this
                gives you the empty string, effectively suppressing the
                summary.
            initial (bool): Whether to capitialize the first letter. Default is
                True.
            default (str): What to give if there's no component defined.

        Returns:
            str: A summary string.

        Example:

            r = Component({'colour': 'Red',
                           'grainsize': 'VF-F',
                           'lithology': 'Sandstone'})

            r.summary()  -->  'Red, vf-f, sandstone'
        """
        if default and not self.__dict__:
            return default

        if fmt == '':
            return default

        keys = [k for k, v in self.__dict__.items() if v is not '']

        f = fmt or '{' + '}, {'.join(keys) + '}'

        try:
            summary = CustomFormatter().format(f, **self.__dict__)
        except KeyError as e:
            raise ComponentError("Error building summary, "+str(e))

        if summary and initial and not fmt:
            summary = summary[0].upper() + summary[1:]

        return summary

def
summary
(
self
,
fmt
=
,
initial
=
True
,
default
=
''
)
:
if
default
and
not
self
.
__dict__
:
return
default
if
fmt
==
''
:
return
default
keys
=
[
k
for
k
,
v
in
self
.
__dict__
.
items
(
)
if
v
is
not
''
]
f
=
fmt
or
'{'
+
'}, {'
.
join
(
keys
)
+
'}'
try
:
summary
=
CustomFormatter
(
)
.
format
(
f
,
**
self
.
__dict__
)
except
KeyError
as
e
:
raise
ComponentError
(
"Error building summary, "
+
str
(
e
)
)
if
summary
and
initial
and
not
fmt
:
summary
=
summary
[
0
]
.
upper
(
)
+
summary
[
1
:
]
return
summary

Given a format string return a summary description of a component .


def Rock(*args, **kwargs):
    """
    Graceful deprecation for old class name.
    """

    with warnings.catch_warnings():
        warnings.simplefilter("always")
        w = "The 'Rock' class was renamed 'Component'. "
        w += "Please update your code."
        warnings.warn(w, DeprecationWarning, stacklevel=2)

    return Component(*args, **kwargs)

Graceful deprecation for old class name .


def _process_row(text, columns):
    """
    Processes a single row from the file.
    """
    if not text:
        return

    # Construct the column dictionary that maps each field to
    # its start, its length, and its read and write functions.
    coldict = {k: {'start': s,
                   'len': l,
                   'read': r,
                   'write': w} for k, (s, l, r, w) in columns.items()}

    # Now collect the item
    item = {}
    for field in coldict:
        value = _get_field(text, coldict, field)
        if value is not None:
            item[field] = value

    return item

def _process_row ( text , columns ) : if not text : return coldict = { k : { 'start' : s , 'len' : l , 'read' : r , 'write' : w } for k , ( s , l , r , w ) in columns . items ( ) } item = { } for field in coldict : value = _get_field ( text , coldict , field ) if value is not None : item [ field ] = value return item


def parse_canstrat(text):
    """
    Read all the rows and return a dict of the results.
    """
    result = {}
    for row in text.split('\n'):
        if not row:
            continue

        if len(row) < 8:  # Not a real record.
            continue

        # Read the metadata for this row/
        row_header = _process_row(row, columns_) or {'card': None}
        card = row_header['card']

        # Now we know the card type for this row, we can process it.
        if card is not None:
            item = _process_row(row, columns[card])

        this_list = result.get(card, [])
        this_list.append(item)
        result[card] = this_list

    # Flatten if possible.
    for c, d in result.items():
        if len(d) == 1:
            result[c] = d[0]

    return result

def parse_canstrat ( text ) : result = { } for row in text . split ( '\n' ) : if not row : continue if len ( row ) < 8 : continue row_header = _process_row ( row , columns_ ) or { 'card' : None } card = row_header [ 'card' ] if card is not None : item = _process_row ( row , columns [ card ] ) this_list = result . get ( card , [ ] ) this_list . append ( item ) result [ card ] = this_list for c , d in result . items ( ) : if len ( d ) == 1 : result [ c ] = d [ 0 ] return result

Read all the rows and return a dict of the results .


def __strict(self):
        """
        Private method. Checks if striplog is monotonically increasing in
        depth.

        Returns:
            Bool.
        """
        def conc(a, b):
            return a + b

        # Check boundaries, b
        b = np.array(reduce(conc, [[i.top.z, i.base.z] for i in self]))

        return all(np.diff(b) >= 0)

def __strict ( self ) : def conc ( a , b ) : return a + b b = np . array ( reduce ( conc , [ [ i . top . z , i . base . z ] for i in self ] ) ) return all ( np . diff ( b ) >= 0 )

Private method . Checks if striplog is monotonically increasing in depth .


def unique(self):
        """
        Property. Summarize a Striplog with some statistics.

        Returns:
            List. A list of (Component, total thickness thickness) tuples.
        """
        all_rx = set([iv.primary for iv in self])
        table = {r: 0 for r in all_rx}
        for iv in self:
            table[iv.primary] += iv.thickness

        return sorted(table.items(), key=operator.itemgetter(1), reverse=True)

def unique ( self ) : all_rx = set ( [ iv . primary for iv in self ] ) table = { r : 0 for r in all_rx } for iv in self : table [ iv . primary ] += iv . thickness return sorted ( table . items ( ) , key = operator . itemgetter ( 1 ) , reverse = True )

Property . Summarize a Striplog with some statistics .


def __intervals_from_tops(self,
                              tops,
                              values,
                              basis,
                              components,
                              field=None,
                              ignore_nan=True):
        """
        Private method. Take a sequence of tops in an arbitrary dimension,
        and provide a list of intervals from which a striplog can be made.

        This is only intended to be used by ``from_image()``.

        Args:
            tops (iterable). A list of floats.
            values (iterable). A list of values to look up.
            basis (iterable). A list of components.
            components (iterable). A list of Components.

        Returns:
            List. A list of Intervals.
        """
        # Scale tops to actual depths.
        length = float(basis.size)
        start, stop = basis[0], basis[-1]
        tops = [start + (p/(length-1)) * (stop-start) for p in tops]
        bases = tops[1:] + [stop]

        list_of_Intervals = []
        for i, t in enumerate(tops):

            v, c, d = values[i], [], {}

            if ignore_nan and np.isnan(v):
                continue

            if (field is not None):
                d = {field: v}

            if components is not None:
                try:
                    c = [deepcopy(components[int(v)])]
                except IndexError:
                    c = []

                if c and (c[0] is None):
                    c = []

            interval = Interval(t, bases[i], data=d, components=c)
            list_of_Intervals.append(interval)

        return list_of_Intervals

def __intervals_from_tops ( self , tops , values , basis , components , field = None , ignore_nan = True ) : length = float ( basis . size ) start , stop = basis [ 0 ] , basis [ - 1 ] tops = [ start + ( p / ( length - 1 ) ) * ( stop - start ) for p in tops ] bases = tops [ 1 : ] + [ stop ] list_of_Intervals = [ ] for i , t in enumerate ( tops ) : v , c , d = values [ i ] , [ ] , { } if ignore_nan and np . isnan ( v ) : continue if ( field is not None ) : d = { field : v } if components is not None : try : c = [ deepcopy ( components [ int ( v ) ] ) ] except IndexError : c = [ ] if c and ( c [ 0 ] is None ) : c = [ ] interval = Interval ( t , bases [ i ] , data = d , components = c ) list_of_Intervals . append ( interval ) return list_of_Intervals

Private method . Take a sequence of tops in an arbitrary dimension and provide a list of intervals from which a striplog can be made .


def _clean_longitudinal_data(cls, data, null=None):
        """
        Private function. Make sure we have what we need to make a striplog.
        """

        # Rename 'depth' or 'MD'
        if ('top' not in data.keys()):
            data['top'] = data.pop('depth', data.pop('MD', None))

        # Sort everything
        idx = list(data.keys()).index('top')
        values = sorted(zip(*data.values()), key=lambda x: x[idx])
        data = {k: list(v) for k, v in zip(data.keys(), zip(*values))}

        if data['top'] is None:
            raise StriplogError('Could not get tops.')

        # Get rid of null-like values if specified.
        if null is not None:
            for k, v in data.items():
                data[k] = [i if i != null else None for i in v]

        return data

def
_clean_longitudinal_data
(
cls
,
data
,
=
)
:
if
(
'top'
not
in
data
.
keys
(
)
)
:
data
[
'top'
]
=
data
.
pop
(
'depth'
,
data
.
pop
(
'MD'
,
)
)
idx
=
list
(
data
.
keys
(
)
)
.
index
(
'top'
)
values
=
sorted
(
zip
(
*
data
.
values
(
)
)
,
key
=
lambda
x
:
x
[
idx
]
)
data
=
{
k
:
list
(
v
)
for
k
,
v
in
zip
(
data
.
keys
(
)
,
zip
(
*
values
)
)
}
if
data
[
'top'
]
is
:
raise
StriplogError
(
'Could not get tops.'
)
if
is
not
:
for
k
,
v
in
data
.
items
(
)
:
data
[
k
]
=
[
i
if
i
!=
else
for
i
in
v
]
return
data

Private function . Make sure we have what we need to make a striplog .


def from_petrel(cls, filename,
                    stop=None,
                    points=False,
                    null=None,
                    function=None,
                    include=None,
                    exclude=None,
                    remap=None,
                    ignore=None):

        """
        Makes a striplog from a Petrel text file.

        Returns:
            striplog.
        """
        result = utils.read_petrel(filename,
                                   function=function,
                                   remap=remap,
                                   )

        data = cls._clean_longitudinal_data(result,
                                            null=null
                                            )

        list_of_Intervals = cls._build_list_of_Intervals(data,
                                                         stop=stop,
                                                         points=points,
                                                         include=include,
                                                         exclude=exclude,
                                                         ignore=ignore
                                                         )
        if list_of_Intervals:
            return cls(list_of_Intervals)
        return None

def from_petrel ( cls , filename , stop = None , points = False , null = None , function = None , include = None , exclude = None , remap = None , ignore = None ) : result = utils . read_petrel ( filename , function = function , remap = remap , ) data = cls . _clean_longitudinal_data ( result , null = null ) list_of_Intervals = cls . _build_list_of_Intervals ( data , stop = stop , points = points , include = include , exclude = exclude , ignore = ignore ) if list_of_Intervals : return cls ( list_of_Intervals ) return None

Makes a striplog from a Petrel text file .


def _build_list_of_Intervals(cls,
                                 data_dict,
                                 stop=None,
                                 points=False,
                                 include=None,
                                 exclude=None,
                                 ignore=None,
                                 lexicon=None):
        """
        Private function. Takes a data dictionary and reconstructs a list
        of Intervals from it.

        Args:
            data_dict (dict)
            stop (float): Where to end the last interval.
            points (bool)
            include (dict)
            exclude (dict)
            ignore (list)
            lexicon (Lexicon)

        Returns:
            list.
        """

        include = include or {}
        exclude = exclude or {}
        ignore = ignore or []

        # Reassemble as list of dicts
        all_data = []
        for data in zip(*data_dict.values()):
            all_data.append({k: v for k, v in zip(data_dict.keys(), data)})

        # Sort
        all_data = sorted(all_data, key=lambda x: x['top'])

        # Filter down:
        wanted_data = []
        for dictionary in all_data:
            keep = True
            delete = []
            for k, v in dictionary.items():
                incl = include.get(k, utils.null_default(True))
                excl = exclude.get(k, utils.null_default(False))
                if k in ignore:
                    delete.append(k)
                if not incl(v):
                    keep = False
                if excl(v):
                    keep = False
            if delete:
                for key in delete:
                    _ = dictionary.pop(key, None)
            if keep:
                wanted_data.append(dictionary)

        # Fill in
        if not points:
            for i, iv in enumerate(wanted_data):
                if iv.get('base', None) is None:
                    try:  # To set from next interval
                        iv['base'] = wanted_data[i+1]['top']
                    except (IndexError, KeyError):
                        # It's the last interval
                        if stop is not None:
                            thick = stop - iv['top']
                        else:
                            thick = 1
                        iv['base'] = iv['top'] + thick

        # Build the list of intervals to pass to __init__()
        list_of_Intervals = []
        for iv in wanted_data:
            top = iv.pop('top')
            base = iv.pop('base', None)
            descr = iv.pop('description', '')
            if iv:
                c, d = {}, {}
                for k, v in iv.items():
                    if (k[:5].lower() == 'comp ') or (k[:9].lower() == 'component'):
                        k = re.sub(r'comp(?:onent)? ', '', k, flags=re.I)
                        c[k] = v  # It's a component
                    else:
                        if v is not None:
                            d[k] = v  # It's data
                comp = [Component(c)] if c else None
                this = Interval(**{'top': top,
                                   'base': base,
                                   'description': descr,
                                   'data': d,
                                   'components': comp})
            else:
                this = Interval(**{'top': top,
                                   'base': base,
                                   'description': descr,
                                   'lexicon': lexicon})
            list_of_Intervals.append(this)

        return list_of_Intervals

def
_build_list_of_Intervals
(
cls
,
data_dict
,
stop
=
,
points
=
False
,
include
=
,
exclude
=
,
ignore
=
,
lexicon
=
)
:
include
=
include
or
{
}
exclude
=
exclude
or
{
}
ignore
=
ignore
or
[
]
all_data
=
[
]
for
data
in
zip
(
*
data_dict
.
values
(
)
)
:
all_data
.
append
(
{
k
:
v
for
k
,
v
in
zip
(
data_dict
.
keys
(
)
,
data
)
}
)
all_data
=
sorted
(
all_data
,
key
=
lambda
x
:
x
[
'top'
]
)
wanted_data
=
[
]
for
dictionary
in
all_data
:
keep
=
True
delete
=
[
]
for
k
,
v
in
dictionary
.
items
(
)
:
incl
=
include
.
get
(
k
,
utils
.
null_default
(
True
)
)
excl
=
exclude
.
get
(
k
,
utils
.
null_default
(
False
)
)
if
k
in
ignore
:
delete
.
append
(
k
)
if
not
incl
(
v
)
:
keep
=
False
if
excl
(
v
)
:
keep
=
False
if
delete
:
for
key
in
delete
:
_
=
dictionary
.
pop
(
key
,
)
if
keep
:
wanted_data
.
append
(
dictionary
)
if
not
points
:
for
i
,
iv
in
enumerate
(
wanted_data
)
:
if
iv
.
get
(
'base'
,
)
is
:
try
:
iv
[
'base'
]
=
wanted_data
[
i
+
1
]
[
'top'
]
except
(
IndexError
,
KeyError
)
:
if
stop
is
not
:
thick
=
stop
iv
[
'top'
]
else
:
thick
=
1
iv
[
'base'
]
=
iv
[
'top'
]
+
thick
list_of_Intervals
=
[
]
for
iv
in
wanted_data
:
top
=
iv
.
pop
(
'top'
)
base
=
iv
.
pop
(
'base'
,
)
descr
=
iv
.
pop
(
'description'
,
''
)
if
iv
:
c
,
d
=
{
}
,
{
}
for
k
,
v
in
iv
.
items
(
)
:
if
(
k
[
:
5
]
.
lower
(
)
==
'comp '
)
or
(
k
[
:
9
]
.
lower
(
)
==
'component'
)
:
k
=
re
.
sub
(
r'comp(?:onent)? '
,
''
,
k
,
flags
=
re
.
I
)
c
[
k
]
=
v
else
:
if
v
is
not
:
d
[
k
]
=
v
comp
=
[
Component
(
c
)
]
if
c
else
this
=
Interval
(
**
{
'top'
:
top
,
'base'
:
base
,
'description'
:
descr
,
'data'
:
d
,
'components'
:
comp
}
)
else
:
this
=
Interval
(
**
{
'top'
:
top
,
'base'
:
base
,
'description'
:
descr
,
'lexicon'
:
lexicon
}
)
list_of_Intervals
.
append
(
this
)
return
list_of_Intervals

Private function . Takes a data dictionary and reconstructs a list of Intervals from it .


def from_csv(cls, filename=None,
                 text=None,
                 dlm=',',
                 lexicon=None,
                 points=False,
                 include=None,
                 exclude=None,
                 remap=None,
                 function=None,
                 null=None,
                 ignore=None,
                 source=None,
                 stop=None,
                 fieldnames=None):
        """
        Load from a CSV file or text.
        """
        if (filename is None) and (text is None):
            raise StriplogError("You must provide a filename or CSV text.")

        if (filename is not None):
            if source is None:
                source = filename
            with open(filename, 'r') as f:
                text = f.read()

        source = source or 'CSV'

        # Deal with multiple spaces in space delimited file.
        if dlm == ' ':
            text = re.sub(r'[ \t]+', ' ', text)

        if fieldnames is not None:
            text = dlm.join(fieldnames) + '\n' + text

        try:
            f = StringIO(text)  # Python 3
        except TypeError:
            f = StringIO(unicode(text))  # Python 2

        reader = csv.DictReader(f, delimiter=dlm)

        # Reorganize the data to make fixing it easier.
        reorg = {k.strip().lower(): [] for k in reader.fieldnames if k is not None}
        t = f.tell()
        for key in reorg:
            f.seek(t)
            for r in reader:
                s = {k.strip().lower(): v.strip() for k, v in r.items()}
                try:
                    reorg[key].append(float(s[key]))
                except ValueError:
                    reorg[key].append(s[key])

        f.close()

        remap = remap or {}
        for k, v in remap.items():
            reorg[v] = reorg.pop(k)

        data = cls._clean_longitudinal_data(reorg, null=null)

        list_of_Intervals = cls._build_list_of_Intervals(data,
                                                         points=points,
                                                         lexicon=lexicon,
                                                         include=include,
                                                         exclude=exclude,
                                                         ignore=ignore,
                                                         stop=stop)

        return cls(list_of_Intervals, source=source)