anyblok_wms_base.core.physobj.main.
PhysObj
[source]¶Main data type to represent physical objects managed by the system.
The instances of this model are also the ultimate representation of the PhysObj “staying the same” or “becoming different” under the Operations, which is, ultimately, a subjective decision that has to be left to downstream libraires and applications, or even end users.
For instance, everybody agrees that moving something around does not make it different. Therefore, the Move Operation uses the same PhysObj record in its outcome as in its input. On the other hand, changing a property could be considered enough an alteration of the physical object to consider it different, or not (think of recording some measurement that had not be done earlier.)
id
= <anyblok.column.Integer object>¶Primary key.
type
= <anyblok.relationship.Many2One object>¶The PhysObj Type
properties
= <anyblok.relationship.Many2One object>¶Link to Properties
.
See also
Properties for functional aspects.
Warning
don’t ever mutate the contents of properties
directly,
unless what you want is precisely to affect all the PhysObj
records that use them directly.
Besides their type
and the fields meant for the Wms Base
bloks logic, the PhysObj Model bears flexible data,
called properties, that are to be
manipulated as key/value pairs through the get_property()
and
set_property()
methods.
As far as wms_core
is concerned, values of properties can be of any
type, yet downstream applications and libraries can choose to make them
direct fields of the Properties
model.
Properties can be shared among several PhysObj records, for efficiency.
The set_property()
implements the necessary Copy-on-Write
mechanism to avoid unintentionnally modify the properties of many
PhysObj records.
Technically, this data is deported into the Properties
Model (see there on how to add additional properties). The properties
column value can be None, so that we don’t pollute the database with
empty lines of Property records, although this is subject to change
in the future.
has_type
(goods_type)[source]¶Tell whether self
has the given type.
Parameters: | goods_type (type.Type) – |
---|---|
Returns: | True if the type attribute is goods_type or
on of its descendants. |
Rtype bool: |
get_property
(k, default=None)[source]¶Property getter, works like dict.get()
.
Actually I’d prefer to simply implement the dict API, but we can’t direcly inherit from UserDict yet. This is good enough to provide the abstraction needed for current internal wms_core calls.
merged_properties
()[source]¶Return all Properties, merged with the Type properties.
Return type: | dict |
---|
To retrieve just one Property, prefer get_property()
, which
is meant to be more efficient.
set_property
(k, v)[source]¶Property setter.
See remarks on get_property()
.
This method implements a simple Copy-on-Write mechanism. Namely, if the properties are referenced by other PhysObj records, it will duplicate them before actually setting the wished value.
update_properties
(mapping)[source]¶Update Properties in one shot, similar to dict.update()
Parameters: | mapping – a dict like object, or an iterable of
(key, value) pairs |
---|
This method implements a simple Copy-on-Write mechanism. Namely, if the properties are referenced by other PhysObj records, it will duplicate them before actually setting the wished value.
current_avatar
()[source]¶The Avatar giving the current position of self
in reality
Returns: | the Avatar, or None , in case self is not yet or
no more physically present. |
---|
eventual_avatar
()[source]¶The Avatar giving the latest foreseeable position of self
.
Returns: | the Avatar, or None , in case
|
---|
There are more complicated corner cases, but they shouldn’t arise in
real operation and the results are considered to be dependent on the
implementation of this method, hence not part of any stability
promises. Simplest example: a single avatar, with state='present'
and dt_until
not None
(normally a subsequent avatar, in the
planned
state should explain the bounded time range).
flatten_containers_subquery
(top=None, additional_states=None, at_datetime=None)[source]¶Return an SQL subquery flattening the containment graph.
Containing PhysObj can themselves be placed within a container
through the standard mechanism: by having an Avatar whose location is
the surrounding container.
This default implementation issues a recursive CTE (WITH RECURSIVE
)
that climbs down along this, returning just the id
column
This subquery cannot be used directly: it is meant to be used as part
of a wider query; see unit tests
) for nice
examples with or without joins.
Note
This subquery itself does not restrict its results to actually be containers! Only its use in joins as locations of Avatars will, and that’s considered good enough, as filtering on actual containers would be more complicated (resolving behaviour inheritance) and is useless for quantity queries.
Applicative code relying on this method for other reasons than quantity counting should therefore add its own ways to restrict to actual containers if needed.
Parameters: | top – if specified, the query starts at this Location (inclusive) |
---|
For some applications with a large and complicated containing hierarchy, joining on this CTE can become a performance problem. Quoting PostgreSQL documentation on CTEs:
However, the other side of this coin is that the optimizer is less able to push restrictions from the parent query down into a WITH query than an ordinary subquery. The WITH query will generally be evaluated as written, without suppression of rows that the parent query might discard afterwards.
If that becomes a problem, it is still possible to override the present method: any subquery whose results have the same columns can be used by callers instead of the recursive CTE.
Examples:
code
to express inclusion instead of the standard Avatar
mechanism.
parent
. See this test
for a proof of this concept.anyblok_wms_base.core.physobj.type.
Type
[source]¶Types of PhysObj.
For a full functional discussion, see Physical Object Types.
id
= <anyblok.column.Integer object>¶Primary key
code
= <anyblok.column.Text object>¶Uniquely identifying code.
As a convenience, and for sharing with other applications.
behaviours
= <anyblok_postgres.column.Jsonb object>¶Flexible field to encode how represented objects interact with the system.
Notably, PhysObj Types specify with this flexible field how various
Operations
will treat the represented physical object.
See also
Unpack
for a complex example.
But behaviours are in no means in one to one correspondence with Operation classes, nor do they need to be related to Operations. Any useful information that depends on the Type only is admissible to encode as a behaviour.
The value is a key/value mapping (behaviour name/value).
Warning
direct read access to a behaviour is to be
avoided in favour of get_behaviour()
(see Goods Type hierarchy and behaviour inheritance).
This field is also open for downstream libraries and applications to
make use of it to define some of their specific logic, but care must be
taken not to conflict with the keys used by wms-core
and other bloks
(TODO introduce namespacing, then ? at least make a list available by
using constants from an autodocumented module)
is_sub_type
(gt)[source]¶True if self
is a sub type of gt
, inclusively.
TODO PERF the current implementation recurses over ancestors. A subsequent implementation could add caching and/or recursive SQL queries.
query_subtypes
(ancestors, as_cte=False)[source]¶Return a recursive SQL query for Type hierarchy.
Parameters: | ancestors – the Type to start with (inclusive) |
---|
The resulting query or CTE matches all Types that are descendents
(inclusive) of those in ancestors
.
get_behaviour
(name, default=None)[source]¶Get the value of the behaviour with given name.
This method is the preferred way to access a given behaviour.
It resolves the wished behaviour by looking it up within the
behaviours
dict
, and recursively on its parent.
It also takes care of corner cases, such as when behaviours
is
None
as a whole.
query_behaviour
(behaviour, as_cte=False)[source]¶Return a recursive SQL query for behaviour presence.
Parameters: |
|
---|
The resulting query or CTE matches all Types that are descendents
(inclusive) of those in ancestors
.
anyblok_wms_base.core.physobj.main.
Properties
[source]¶Properties of PhysObj.
This is kept in a separate Model (and SQL table) to provide sharing
among several PhysObj
instances, as they can turn out to be
identical for a large number of them.
Use-case: receive a truckload of milk bottles that all have the same expiration date, and unpack everything down to the bottles. The expiration date would be stored in a single Properties instance, assuming there aren’t also non-uniform properties to store, of course.
Applications are welcome to overload this model to add new fields rather
than storing their meaningful information in the flexible
field,
if it has added value for performance or programmming tightness reasons.
This has the obvious drawback of defining some properties for all PhysObj,
regardless of their Types, so it should not be abused.
This model implements a subset of the dict
API, treating
direct fields and top-level keys of flexible
uniformely, so
that, as long as all pieces of code use only this API to handle properties,
flexible keys can be replaced with proper fields transparently at any time
in the development of downstream applications and libraries
(assuming of course that any existing data is properly migrated to the new
schema).
id
= <anyblok.column.Integer object>¶Primary key.
flexible
= <anyblok_postgres.column.Jsonb object>¶Flexible properties.
The value is expected to be a mapping, and all property handling
operations defined in the wms-core
will handle the properties by key,
while being indifferent of the values.
Note
the core also makes use of a few special properties, such as
contents
. TODO make a list, in the form of
constants in a module
create
(**props)[source]¶Direct creation.
The caller doesn’t have to care about which properties get stored as
direct fields or in the flexible
field.
This method is a better alternative than
insertion followed by calls to set()
, because it guarantees that
only one SQL INSERT will be issued.
If no props
are given, then nothing is created and None
gets returned, thus avoiding a needless row in the database.
This may seem trivial, but it spares a test for callers that would
pass a dict
, using the **
syntax, which could turn out to
be empty.
update
(*args, **kwargs)[source]¶Similar to dict.update()
This current implementation doesn’t attempt to be smarter that setting the values one after the other, which means in particular going through all the checks for each key. A future implementation might try and be more efficient.
anyblok_wms_base.core.physobj.main.
Avatar
[source]¶PhysObj Avatar.
See in Core Concepts for a functional description.
outcome_of
= <anyblok.relationship.Many2One object>¶The Operation that created this Avatar.
location
= <anyblok.relationship.Many2One object>¶Where the PhysObj are/will be/were.
See Location
for a discussion of what this should actually mean.
state
= <anyblok.column.Selection object>¶State of existence in the premises.
see anyblok_wms_base.constants
.
This may become an ENUM once Anyblok supports them.
dt_from
= <anyblok.column.DateTime object>¶Date and time from which the Avatar is meaningful, inclusively.
Functionally, even though the default in creating Operations will be to use the current date and time, this is not to be confused with the time of creation in the database, which we don’t care much about.
The actual meaning really depends on the value of the state
field:
past
and present
states, this is supposed to be
a faithful representation of reality.future
state, this is completely theoretical, and
wms-core
doesn’t do much about it, besides using it to avoid
counting several Avatars of the same physical goods
while peeking at quantities in the future
.
If the end application does serious time prediction, it can use it
freely.In all cases, this doesn’t mean that the very same PhysObj aren’t present at an earlier time with the same state, location, etc. That earlier time range would simply be another Avatar (use case: moving back and forth).
dt_until
= <anyblok.column.DateTime object>¶Date and time until which the Avatar record is meaningful, exclusively.
Like dt_from
, the meaning varies according to the value of
state
:
past
state, this is supposed to be a faithful
representation of reality: apart from the special case of formal
Splits and Aggregates, the goods
really left this location at these date and time.present
and future
states, this is purely
theoretical, and the same remarks as for the dt_from
field
apply readily.In all cases, this doesn’t mean that the very same goods aren’t present at an later time with the same state, location, etc. That later time range would simply be another Avatar (use case: moving back and forth).
id
= <anyblok.column.Integer object>¶Primary key.