367 lines
14 KiB
Python
367 lines
14 KiB
Python
# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
from enum import Enum
|
|
import logging
|
|
|
|
from .notes import MAX_VERBOSITY
|
|
from .notes import MIN_VERBOSITY
|
|
from .notes import Query
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class _NoteType:
|
|
"""Define the patterns and pertinent positions of information for a note
|
|
|
|
:param root: the string used as the initial identifier for the type
|
|
:param key_pattern_count: the number of variable positions in the key.
|
|
E.g.: a value of 3, and a root of "tacos" would generate a key_pattern
|
|
of "tacos/{}/{}/{}", while a value of 0 would generate "tacos".
|
|
The lookup_pattern for a type is also drived from the key_pattern_count
|
|
by subtracting 1 (minimum 0), such that a value of 3 and a root of
|
|
"tacos" would generate a lookup_pattern of "tacos/{}/{}/
|
|
:param id_start: the start location in the assoc_id of a note of this type
|
|
where the id of the item it is associated with appears.
|
|
:param id_end: the optional end location in the assoc_id for locating the
|
|
id from the assoc_id of a note of this type. This is only valid for
|
|
items that have a fixed length key.
|
|
:param default_subtype: the default sub_type specified upon creation of a
|
|
note of this type.
|
|
"""
|
|
|
|
def __init__(self, root, key_pattern_count, id_start,
|
|
id_end=None, default_subtype="metadata"):
|
|
self.root = root
|
|
self.base_pattern = "{}/".format(root)
|
|
self.kp_count = key_pattern_count
|
|
self.key_pattern = "{}{}".format(root, "/{}" * self.kp_count)
|
|
self.lp_count = self.kp_count - 1 if self.kp_count > 0 else 0
|
|
self.lookup_pattern = "{}/{}".format(root, "{}/" * self.lp_count)
|
|
self.id_start = id_start
|
|
self.id_end = id_end
|
|
self.default_subtype = default_subtype
|
|
|
|
|
|
class NoteType(Enum):
|
|
|
|
# Action
|
|
#
|
|
# Text: action/12345678901234567890123456
|
|
# | |
|
|
# Position: 0....5.|..1....1....2....2....3..|.3
|
|
# | 0 5 0 5 0 | 5
|
|
# | |
|
|
# (7) ACTION_ID_START |
|
|
# (33) ACTION_ID_END
|
|
ACTION = _NoteType(
|
|
root="action",
|
|
key_pattern_count=1,
|
|
id_start=7,
|
|
id_end=33,
|
|
default_subtype="action metadata")
|
|
|
|
# Step
|
|
#
|
|
# Text: step/12345678901234567890123456/my_step
|
|
# | ||
|
|
# Position: 0....5....1....1....2....2....3||..3....4
|
|
# | 0 5 0 5 0|| 5 0
|
|
# | |\
|
|
# (5) STEP_ACTION_ID_START | \
|
|
# | (32) STEP_ID_START
|
|
# (31) STEP_ACTION_ID_END
|
|
STEP = _NoteType(
|
|
root="step",
|
|
key_pattern_count=2,
|
|
id_start=32,
|
|
default_subtype="step metadata")
|
|
|
|
OTHER = _NoteType(root="", key_pattern_count=0, id_start=0)
|
|
|
|
@property
|
|
def base_pattern(self):
|
|
return self.value.base_pattern
|
|
|
|
@property
|
|
def key_pattern(self):
|
|
return self.value.key_pattern
|
|
|
|
@property
|
|
def lookup_pattern(self):
|
|
return self.value.lookup_pattern
|
|
|
|
@property
|
|
def id_start(self):
|
|
return self.value.id_start
|
|
|
|
@property
|
|
def id_end(self):
|
|
return self.value.id_end
|
|
|
|
@property
|
|
def default_subtype(self):
|
|
return self.value.default_subtype
|
|
|
|
@classmethod
|
|
def get_type(cls, note):
|
|
for tp in cls:
|
|
if note.assoc_id.startswith(tp.value.base_pattern):
|
|
return tp
|
|
return cls.OTHER
|
|
|
|
|
|
class NotesHelper:
|
|
"""Notes Helper
|
|
|
|
Provides helper methods for the common use cases for notes
|
|
:param notes_manager: the NotesManager object to use
|
|
"""
|
|
def __init__(self, notes_manager):
|
|
self.nm = notes_manager
|
|
|
|
def _failsafe_make_note(self, assoc_id, subject, sub_type, note_val,
|
|
verbosity=MIN_VERBOSITY, link_url=None,
|
|
is_auth_link=None, note_timestamp=None):
|
|
"""LOG and continue on any note creation failure"""
|
|
try:
|
|
self.nm.create(
|
|
assoc_id=assoc_id,
|
|
subject=subject,
|
|
sub_type=sub_type,
|
|
note_val=note_val,
|
|
verbosity=verbosity,
|
|
link_url=link_url,
|
|
is_auth_link=is_auth_link,
|
|
note_timestamp=note_timestamp
|
|
)
|
|
except Exception as ex:
|
|
LOG.warning(
|
|
"Creating note for {} encountered a problem, exception info "
|
|
"follows, but processing is not halted for notes.",
|
|
assoc_id
|
|
)
|
|
LOG.exception(ex)
|
|
|
|
def _failsafe_get_notes(self, assoc_id_pattern, verbosity,
|
|
exact_match):
|
|
"""LOG and continue on any note retrieval failure"""
|
|
try:
|
|
if verbosity < MIN_VERBOSITY:
|
|
return []
|
|
q = Query(assoc_id_pattern, verbosity, exact_match)
|
|
return self.nm.retrieve(q)
|
|
except Exception as ex:
|
|
LOG.warning(
|
|
"Note retrieval for {} encountered a problem, exception "
|
|
"info follows, but processing is not halted for notes.",
|
|
assoc_id_pattern
|
|
)
|
|
LOG.exception(ex)
|
|
return []
|
|
|
|
#
|
|
# Retrieve notes by note ID
|
|
#
|
|
|
|
def get_note(self, note_id):
|
|
"""Return a single note looked up by the specified note_id
|
|
|
|
:param note_id: the ULID of the note to retrieve.
|
|
:raises NoteNotFoundError: if there is no note matching the requested
|
|
note_id
|
|
"""
|
|
return self.nm.retrieve_by_id(note_id)
|
|
|
|
def get_note_details(self, note):
|
|
"""Return the note details for the specified note
|
|
|
|
:param note: the Note object with additional details to retrieve.
|
|
"""
|
|
return self.nm.get_note_url_info(note)
|
|
|
|
def get_note_assoc_id_type(self, note):
|
|
"""Return the type of note based on the assoc_id
|
|
|
|
:param note: The note to examine
|
|
|
|
The purpose of this method is to use the standard formats (e.g.:
|
|
action and step) supported by this helper to get the type of note.
|
|
This value can be used by a client to enforce access rules to the note
|
|
based on the item it is related to.
|
|
"""
|
|
return NoteType.get_type(note)
|
|
|
|
#
|
|
# Action notes helper methods
|
|
#
|
|
|
|
def make_action_note(self, action_id, note_val, subject=None,
|
|
sub_type=None, verbosity=MIN_VERBOSITY, link_url=None,
|
|
is_auth_link=None, note_timestamp=None):
|
|
"""Creates an action note using a convention for the note's assoc_id
|
|
|
|
:param action_id: the ULID id of an action
|
|
:param note_val: the text for the note
|
|
:param subject: optional subject for the note. Defaults to the
|
|
action_id
|
|
:param sub_type: optional subject type for the note, defaults to
|
|
"action metadata"
|
|
:param verbosity: optional verbosity for the note, defaults to 1,
|
|
i.e.: summary level
|
|
:param link_url: optional link URL if there's additional information
|
|
to retreive from another source.
|
|
:param is_auth_link: optional, defaults to False, indicating if there
|
|
is a need to send a Shipyard service account token with the
|
|
request to the optional URL
|
|
:param note_timestamp: the parseable string timestamp to associate with
|
|
this note. Optional, defaults to the creation time of the note.
|
|
"""
|
|
assoc_id = NoteType.ACTION.key_pattern.format(action_id)
|
|
if subject is None:
|
|
subject = action_id
|
|
if sub_type is None:
|
|
sub_type = NoteType.ACTION.default_subtype
|
|
|
|
self._failsafe_make_note(
|
|
assoc_id=assoc_id,
|
|
subject=subject,
|
|
sub_type=sub_type,
|
|
note_val=note_val,
|
|
verbosity=verbosity,
|
|
link_url=link_url,
|
|
is_auth_link=is_auth_link,
|
|
note_timestamp=note_timestamp
|
|
)
|
|
|
|
def get_all_action_notes(self, verbosity=MIN_VERBOSITY):
|
|
"""Retrieve notes for all actions, in a dictionary keyed by action id.
|
|
|
|
:param verbosity: optional integer, 0-5, the maximum verbosity level
|
|
to retrieve, defaults to 1 (most summary level)
|
|
if set to less than 1, returns {}, skipping any retrieval
|
|
"""
|
|
notes = self._failsafe_get_notes(
|
|
assoc_id_pattern=NoteType.ACTION.lookup_pattern,
|
|
verbosity=verbosity,
|
|
exact_match=False
|
|
)
|
|
note_dict = {}
|
|
id_s = NoteType.ACTION.id_start
|
|
id_e = NoteType.ACTION.id_end
|
|
for n in notes:
|
|
action_id = n.assoc_id[id_s:id_e]
|
|
if action_id not in note_dict:
|
|
note_dict[action_id] = []
|
|
note_dict[action_id].append(n)
|
|
return note_dict
|
|
|
|
def get_action_notes(self, action_id, verbosity=MAX_VERBOSITY):
|
|
"""Retrive notes related to a particular action
|
|
|
|
:param action_id: the action for which to retrieve notes.
|
|
:param verbosity: optional integer, 0-5, the maximum verbosity level
|
|
to retrieve, defaults to 5 (most detailed level)
|
|
if set to less than 1, returns [], skipping any retrieval
|
|
|
|
"""
|
|
return self._failsafe_get_notes(
|
|
assoc_id_pattern=NoteType.ACTION.key_pattern.format(action_id),
|
|
verbosity=verbosity,
|
|
exact_match=True
|
|
)
|
|
|
|
#
|
|
# Step notes helper methods
|
|
#
|
|
|
|
def make_step_note(self, action_id, step_id, note_val, subject=None,
|
|
sub_type=None, verbosity=MIN_VERBOSITY, link_url=None,
|
|
is_auth_link=None, note_timestamp=None):
|
|
"""Creates an action note using a convention for the note's assoc_id
|
|
|
|
:param action_id: the ULID id of the action containing the note
|
|
:param step_id: the step for this note
|
|
:param note_val: the text for the note
|
|
:param subject: optional subject for the note. Defaults to the
|
|
step_id
|
|
:param sub_type: optional subject type for the note, defaults to
|
|
"step metadata"
|
|
:param verbosity: optional verbosity for the note, defaults to 1,
|
|
i.e.: summary level
|
|
:param link_url: optional link URL if there's additional information
|
|
to retreive from another source.
|
|
:param is_auth_link: optional, defaults to False, indicating if there
|
|
is a need to send a Shipyard service account token with the
|
|
request to the optional URL
|
|
:param note_timestamp: the parseable string timestamp to associate with
|
|
this note. Optional, defaults to the creation time of the note.
|
|
"""
|
|
assoc_id = NoteType.STEP.key_pattern.format(action_id, step_id)
|
|
if subject is None:
|
|
subject = step_id
|
|
if sub_type is None:
|
|
sub_type = NoteType.STEP.default_subtype
|
|
|
|
self._failsafe_make_note(
|
|
assoc_id=assoc_id,
|
|
subject=subject,
|
|
sub_type=sub_type,
|
|
note_val=note_val,
|
|
verbosity=verbosity,
|
|
link_url=link_url,
|
|
is_auth_link=is_auth_link,
|
|
note_timestamp=note_timestamp
|
|
)
|
|
|
|
def get_all_step_notes_for_action(self, action_id,
|
|
verbosity=MIN_VERBOSITY):
|
|
"""Retrieve a dict keyed by step names for the action_id
|
|
|
|
:param action_id: the action that contains the target steps
|
|
:param verbosity: optional integer, 0-5, the maximum verbosity level
|
|
to retrieve, defaults to 1 (most summary level)
|
|
if set to less than 1, returns {}, skipping any retrieval
|
|
"""
|
|
notes = self._failsafe_get_notes(
|
|
assoc_id_pattern=NoteType.STEP.lookup_pattern.format(action_id),
|
|
verbosity=verbosity,
|
|
exact_match=False
|
|
)
|
|
note_dict = {}
|
|
id_s = NoteType.STEP.id_start
|
|
for n in notes:
|
|
step_id = n.assoc_id[id_s:]
|
|
if step_id not in note_dict:
|
|
note_dict[step_id] = []
|
|
note_dict[step_id].append(n)
|
|
return note_dict
|
|
|
|
def get_step_notes(self, action_id, step_id, verbosity=MAX_VERBOSITY):
|
|
"""Retrive notes related to a particular step
|
|
|
|
:param action_id: the action containing the step
|
|
:param step_id: the id of the step
|
|
:param verbosity: optional integer, 0-5, the maximum verbosity level
|
|
to retrieve, defaults to 5 (most detailed level)
|
|
if set to less than 1, returns [], skipping any retrieval
|
|
|
|
"""
|
|
return self._failsafe_get_notes(
|
|
assoc_id_pattern=NoteType.STEP.key_pattern.format(
|
|
action_id, step_id),
|
|
verbosity=verbosity,
|
|
exact_match=True
|
|
)
|