extools.view

ExView is a fully functional wrapper around the Extender View object that raises exceptions instead of providing non-zero returns on error for the methods defined in extools.view.ExView.WRAP.

It supports all the methods of the Extender.View class, along with many extra helpers.

On startup an ExView introspects the underlying Sage view to automatically determine:

  • The view’s composition tree
  • The field names
  • The allowed indexes and their key fields

Based on this information, the class automatically configures itself to:

  • Self-compose on request (see extools.view.ExView.compose())
  • Validate orders and keys before errors are raised by Sage
  • Automatically add the correct helpers
    • For detail views, the .lines(), .lines_from(start, end), .lines_where(key=value, key=value, ...) generators and newline() helper.
    • For optional field views, or any view composed with an optional field view, enable the create_optfield, update_optfield, get_optfield, update_or_create_optfield, seek_to_optfield, and delete_optfield helpers.
extools.view.exview(rotoid, index=-1, seek_to={}, fetch=True, compose=False)[source]

Context manager to cleanly open and use an ExView.

Parameters:
  • rotoid (str) – the RotoID of the Sage view.
  • index (int) – the index to open the view with.
  • seek_to (dict|None) – field value mapping to seek to after opening. When set to an empty dictionary, seek to the first line in the view. If set to None, disable seek after opening.
  • fetch (bool) – automatically fetch the first matched record?
  • compose (bool) – automatically compose before seeking?
Raises:

ExViewError

Return type:

None

When called the context manager will yield an open view object. On exit of the block the view will be closed cleanly.

with exview("EX0001") as view:
    try:
        view.recordClear()
        view.browse("")
        view.fetch()
        value = view.get("KEY")
    except ExViewError as err:
        showMessageBox("Failed to get KEY, {}.".format(err))
extools.view.exgen(rotoid, index=-1, seek_to={})[source]

Generator for iterating over all the entries in a view.

Parameters:
  • rotoid (str) – the RotoID of the Sage view.
  • index (int) – the index to open the view with.
  • seek_to (dict|None) – field value mapping to seek to after opening. When set to an empty dictionary, seek to the first line in the view. If set to None, disable seek after opening.
Raises:

ExViewError

Return type:

None

When called, the generator will seek the view to the requested records, or the first record if seek_to is empty. It will then yield all matching rows and then cleanly close the view.

for record in exgen("EX0001"):
    try:
        record.get("FIELD")
    except ExViewError as err:
        showMessageBox("Failed to get FIELD, {}.".format(err))
extools.view.EXVIEW_BLACKLIST = {'OE0999'}

Views that can never be composed with any other.

class extools.view.ExView(rotoid, index=-1, seek_to={}, native_types=False, fetch=True, _root=True, _me=None, _cviews=[])[source]

An exception raising wrapper around the Extender View class.

ExViews can be used to replace repetitive error checking and to take advantage of the try/except/else/finally construct in Python.

Parameters:
  • rotoid (str.) – the RotoID of the Sage view.
  • index (int.) – the index to open the view with.
  • seek_to (dict|None) – field value mapping to seek to after opening. When set to an empty dictionary, seek to the first line in the view. If set to None, disable seek after opening.
Raises:

ExViewError

Return type:

ExView

Replace this:

view = openView("EX0001")
if not view:
    showMessageBox("Failed to open view.")
    return 1
rc = view.recordClear()
if rc != 0:
    showMessageBox("Failed to record clear.")
    return 1
br = view.browse("")
if br != 0:
    showMessageBox("Failed to browse.")
    return 1
fe = view.fetch()
if fe != 0:
    showMessageBox("Failed to fetch.")
    return 1

value = view.get("KEY")

if view:
    view.close()

With this:

try:
    view = ExView("EX0001")
    value = view.get("KEY")
except ExViewError as err:
    showMessageBox("Failed to get KEY, {}.".format(err))
    return 1
finally:
    view.close()

You can even include the traceback using the ExMessages:

try:
    view = ExView("EX0001")
    value = view.get("KEY")
except ExViewError as err:
    # Use ExMessages to display an error level message box and
    # log to a file (if configured). The last exception traceback
    # will be included in both the box and log if ``exc_info=True``.
    exm.error("Failed to get KEY, {}.".format(err), exc_info=True)
    return 1
finally:
    view.close()

ExViews can also self-compose, composing the view and all its related views automatically. Fully composed views require more database operations every time the header is changed and do not perform as well as standalone views or SQL access. However, in cases where performance isn’t paramount you cannot beat the convenience.

from extools import success
from extools.view import ExView
from extools.message import ExMessages

exm = ExMessages("compose-test", ExMessages.DEBUG)

try:
    exv = ExView("OE0520")
    exv.compose()
except Exception as e:
    exm.error("Failed to setup view: {}".format(e), exc_info=True)

# Seek to order ORD000000000064
try:
    # Use index 1, key (ORDNUMBER, )
    exv.order(1)
    exv.seek_to(ORDNUMBER="ORD000000000064")
except Exception as e:
    exm.error("Failed to seek: {}".format(e), exc_info=True)

# Perform an action on each of the detail lines in the order
try:
    for line in exv.oe0500.lines():
        exm.info("Read new line {}".format(line.get("ITEM")))
        # perform many important actions...
except Exception as e:
    exm.error("Failed to perform action: {}".format(e), exc_info=True)
lines(self)

A generator that yields all lines in a detail view.

Only available on detail views.

Return type:None
Yields:ExView
for line in oe500.lines():
    # line now contains the oe0500 view seeked to the next line
lines_from(start, end=None)

A generator that yields all lines from start to end.

Only available on detail views.

Parameters:
  • start (int) – line to start at (numbering starts at 0)
  • end (int) – line to end on (inclusive)
Yields:

ExView

Return type:

None

for line in oe500.lines_from(2, 3):
    # line now contains the oe0500 view seeked to the second line
    # there will be one more iteration with the third line
lines_where(key=value, key=value, key=value, ...)

A generator that yields all lines matched by browsing for the provided keys. All key value pairs are combined using the AND condition and used to browse.

Only available on detail views.

Parameters:
  • key (str) – a field name in the view.
  • value (any) – the value to browse to
Yields:

ExView

Return type:

None

for line in oe500.lines_where(ORDUNIQ=234234):
    # line now contains the oe0500 view seeked to first line or
    # the oder with unqiue key 234234.
create_optfield(field, value)

Create a new optional field, set its value, and save it.

Parameters:
  • field (str) – Optional field name.
  • value (builtins.*) – Optional field value.
Return type:

None

Raises:

ExViewError

try:
    oe0500 = ExView("OE0500")
    oe0500.compose()
    # When the view is composed, the associated optional field view
    # OE0522 is auto-detected so you can call the ``*_optfield``
    # methods directly on oe0500.
    oe0500.create_optfield("MYFIELD", "NEWVAL")
except ExViewError as e:
    # Do something on fail.
update_optfield(field, value)

Update an existing optional field.

Parameters:
  • field (str) – Optional field name.
  • value (builtins.*) – Optional field value.
Return type:

None

Raises:

ExViewError

try:
    oe0500 = ExView("OE0500")
    oe0500.compose()
    # When the view is composed, the associated optional field view
    # OE0522 is auto-detected so you can call the ``*_optfield``
    # methods directly on oe0500.
    oe0500.update_optfield("MYFIELD", "UPDATEDVAL")

    # The composed OE0522 view is also accessible.
    oe0500.oe0522.update_optfield("MYFIELD", "UP2DATEVAL")

except ExViewError as e:
    # Do something on fail.
delete_optfield(field)

Delete an existing optional field.

Parameters:field (str) – Optional field name.
Return type:None
Raises:ExViewError
try:
    oe0500 = ExView("OE0500")
    oe0500.compose()

    # When the view is composed, the associated optional field view
    # OE0522 is auto-detected so you can call the ``*_optfield``
    # methods directly on oe0500.
    oe0500.delete_optfield("MYFIELD")

    # The composed OE0522 view is also accessible.
    oe0500.oe0522.delete_optfield("MYFIELD")

except ExViewError as e:
    # Do something on fail.
get_optfield(field)

Get the value of an existing optional field.

Parameters:field (str) – Optional field name.
Returns:Optional field value.
Return type:builtins.*
Raises:ExViewError
try:
    oe0500 = ExView("OE0500")
    oe0500.compose()

    # When the view is composed, the associated optional field view
    # OE0522 is auto-detected so you can call the ``*_optfield``
    # methods directly on oe0500.
    value = oe0500.get_optfield("MYFIELD")

    # The composed OE0522 view is also accessible.
    value = oe0500.oe0522.get_optfield("MYFIELD")

except ExViewError as e:
    # Do something on fail.
seek_to_optfield(field)

Seek the view to an existing optional field.

Parameters:field (str) – Optional field name.
Return type:None
Raises:ExViewError
try:
    oe0500 = ExView("OE0500")
    oe0500.compose()

    # When the view is composed, the associated optional field view
    # OE0522 is auto-detected so you can call the ``*_optfield``
    # methods directly on oe0500.
    oe0500.seek_to_optfield("MYFIELD")

    # The composed OE0522 view is also accessible.
    # Now that is has seeked to MYFIELD extract the value with get.
    value = oe0500.oe0522.get("VALUE")

except ExViewError as e:
    # Do something on fail.
update_or_create_optfield(field, value)

Update an existing optional field if it exists, otherwise create it.

Parameters:
  • field (str) – Optional field name.
  • value (builtins.*) – Optional field value.
Return type:

None

Raises:

ExViewError

try:
    oe0500 = ExView("OE0500")
    oe0500.compose()
    # When the view is composed, the associated optional field view
    # OE0522 is auto-detected so you can call the ``*_optfield``
    # methods directly on oe0500.
    oe0500.update_or_create_optfield("MYFIELD", "UPDATEDVAL")

    # The composed OE0522 view is also accessible.
    oe0500.oe0522.update_or_create_optfield("MYFIELD", "UPDATEVAL")

except ExViewError as e:
    # Do something on fail.
has_optfield(field)

Check if an optional field exists.

Parameters:field (str) – Optional field name.
Returns:True if an optional field with name field exists.
Return type:bool
Raises:ExViewError
try:
    oe0500 = ExView("OE0500")
    oe0500.compose()
    # When the view is composed, the associated optional field view
    # OE0522 is auto-detected so you can call the ``*_optfield``
    # methods directly on oe0500.
    if oe0500.has_optfield("MYFIELD"):
        showMessageBox("MYFIELD already defined.")

    # The composed OE0522 view is also accessible.
    oe0500.oe0522.has_optfield("MYFIELD", "UPDATEVAL")

except ExViewError as e:
    # Do something on fail.
optfields

Get all optional fields for a view.

Returns:mapping of field names to values.
Return type:dict
Raises:ExViewError
try:
    oe0500 = ExView("OE0500")
    oe0500.compose()

    # oe0500.optfields is now poplated with all the optional
    # fields currently defined for the header.
    # { "FIELDNAME": "VALUE", "F1": 1, "MYFIELD": "VALUE"}
    if "MYFIELD" in oe0500.optfields.keys():
        showMessage("MYFIELD is set to {}".format(
            oe0500.optfields["MYFIELD"]))
except ExViewError as e:
    # Do something on fail.
ATTRS = {'A': 2, 'C': 16, 'E': 4, 'K': 8, 'P': 32, 'R': 48, 'X': 64}
ATTR_A = 2
ATTR_COMPUTED = 16
ATTR_EDITABLE = 4
ATTR_KEY = 8
ATTR_P = 32
ATTR_R = 48
ATTR_X = 64
DETAIL_VIEW_HINTS = {'CNTENTR', 'DETAILNUM', 'ENTRY', 'LINENUM'}

Views containing any one of these fields may be detail views.

OPTFIELD_VIEW_HINTS = {'OPTFIELD'}

Views containing any one of these fields may be optional field views.

WRAP = ['fetchLock', 'readLock', 'insert', 'delete', 'init', 'post', 'process', 'verify', 'recordClear', 'dirty', 'unlock', 'cancel', 'recordGenerate', 'put', 'browse']

These View functions raise an ExViewError on non-zero return.

all(ascending=True)[source]

Generator that yields once for each record in the view.

Raises:ExViewError
Yields:ExView
cached_view(*args, **kwargs)[source]
close(*args, **kwargs)[source]
compose(*args, **kwargs)[source]
compose_from(composed_views)[source]
copy_to(view2, force=True, exclude=[], include=[], post_process=[], skip_keys=True, skip_computed=True, save=False)[source]

Copy the current object to view2.

Parameters:
  • view2 (ExView) – the view to copy to.
  • exclude (str[]) – Fields to exclude from copy.
  • include (str[]) – Fields to include, excluding all others.
  • post_process (int[]) – run process with these processcmds after copy.
  • skip_keys (bool) – skip fields with the Key attribute. Default: yes.
  • skip_computed (bool) – skip fields with the Key attribute. Default: yes.
  • save (bool) – insert the object after copy. Default: no.
Raises:

ExViewError – when any error occurs during the copy.

create(**fields)[source]

Generate and insert a new entry with field/value pairs.

Parameters:fields (field=value) – field value pairs that will be set on the new entry.
Return type:None
Raises:ExViewError
current_key()[source]

Get the current unique key identifying the view record.

Returns:{field: value, field: value…}
exists()[source]

Wrap exists to return True or False and not raise.

Returns:True if record in view exists (has been added), else False
Return type:bool
fetch()[source]

A special wrapper because a non-zero fetch return isn’t an error.

Returns:True if a new line was fetched, else False.
Return type:bool
classmethod from_me(_me)[source]
get(field, _type=-1, size=-1, precision=-1, verify=True)[source]

A special wrapper because get doesn’t return 0 on success.

Parameters:
  • field (str) – field name to get.
  • verify – verify that the field is listed in the view fields?
Type:

bool

Returns:

value in the view.

Return type:

builtin.*

Raises:

ExViewFieldDoesNotExist

has_optfield_view

Is this view composed with an optional field view?

Returns:True if this view is composed with an optional field view.
Return type:bool
is_optfield_view

Is this an optional field view?

Returns:True if this view is an optional field view.
Return type:bool
order(_ord)[source]

Wrap the order to track state in the class as it can’t be queried.

Parameters:index (int) – the index ID to order by.
Return type:None
Raises:ExViewError
parent_key()[source]

Get the current unique key identifying the view record’s parent.

Only relevant for detail views, return the key components before the last one.

The views, as classified by Sage, may either be header, detail, flat or batch. Both detail, and headers with composite keys, may be enumerated.

Returns:{field: value, field: value…}
read()[source]

A special wrapper to raise ExViewRecordDoesNotExist.

Raises:ExViewRecordDoesNotExist
remove_cached_view(*args, **kwargs)[source]
seek_to(fetch=True, **kwargs)[source]

Intelligently seek to a specific entry.

This seek to implementation accepts an arbitrary set of field value pairs and then seeks to the entry using one of three methods:

  • If the current View order index is made up of exactly the fields requested, perform a straight put and read.
  • If the current View has an index made up of exactly the fields requested, temporarily change the index and perform and put a read.
  • If the current View does not have and index made up of exactly the fields requested, attempt to browse and fetch the record.
Parameters:
  • fetch (bool) – fetch after seeking? Default to true.
  • kwargs (dict) – (key)=(value) pairs, where the keys must be the same as the current index keys.
Return type:

None

Raises:

ExViewError

viewid = "OE0500"
try:
    exv = ExView(viewid) # Open Order Details, default view order 0

    # Seek to the 7th line of the order with unique key 1024
    # The default view order is 0: (ORDUNIQ, LINENUM, )
    exv.seek_to(ORDUNIQ=1024, LINENUM=7)

    # Get details from the record and process or update.
    item = exv.get("ITEM")
    ...
except ExViewError as e:

    # The error, "failed to [open|seek]", is contained in the
    # error message.
    showMessage("Error doing something with view {}: {}".format(
        viewid, e))
classmethod table_name(rotoid)[source]
to_dict()[source]

Return all the fields in a view as a dictionary.

Useful for caching full rows for later use.

update(**fields)[source]

Update an entry with field/value pairs.

Parameters:kwargs (field=value) – field value pairs that will be set on the new entry.
Return type:None
Raises:ExViewError
where(**criteria)[source]

Get an ExQuery to retrieve records with the given criteria.

Parameters:criteriafield=value criteria to browse to.
Returns:ExQuery
Raises:ExViewError