# Python - PyDDB
Note for external users - many of the links in the following section refer to internal applications. Please speak to your project team for support.
The pyddb
package provides a clear way for non-developers to interact with DDB by simplifying syntax and implementing convenience features.
This guide will cover installation and quick start of the package, as well as a full reference of the package and its features.
# Features
Besides greatly simplifying the interface for interacting with the DDB API endpoints, the library provides other useful features:
Authentication
The client will prompt the user for their ARUP credentials when the client is instantiated and automatically refresh the token as necessary.
Simplified API and Implementation
The library is built for beginner python users who want to create automated workflows.
We've simplified all microservice endpoints into a single library with a consistent interface. This means there's less to learn and it's easy to get started.
You can keep rerunning scripts and we'll handle checking for duplicates and performing any updates for you. Typically scripts can be used on other projects just by changing the project number.
Easily Explore DDB Taxonomy
DDB has it's own taxonomy: you can't just create a new type of asset or parameter.
This library provides ways of searching through our existing taxonomy by name or uuid.
Fully Tested
This library is fully tested against current and future versions of the DDB API, so you can get started with confidence.
Pagination
By default, our endpoints will return the first page of data. This library handles pagination and retrieves all data for you.
IDE Support
The library is fully typed, so you can get autocomplete and type checking in your IDE.
# Installation and Setup
- Requires Python 3.10+ (App Store) - Required to run the package.
- Requires Git (Service Desk) - Required to install the package.
- Requires GitHub account with Arup email - Required to install the package.
- Recommended Visual Studio Code (App Store) - Recommended environment for writing code.
Once you have the above installed, open Visual Studio Code and open a folder you would like to store your work in.
Open a Bash terminal and run the following commands:
Create a virtual environment
python -m venv .venv
Activate the virtual environment
source .venv/Scripts/activate
Install the DDB Python SDKs
pip install keyring artifacts-keyring python-dateutil
pip install ddb-user-service \
ddb-reference-data-service \
ddb-qa-service \
ddb-parameter-service \
ddb-parameter-metadata-service \
ddb-environment-context-service \
ddb-comments-service \
ddb-template-service \
-i https://pkgs.dev.azure.com/ovearup/_packaging/ddb/pypi/simple/
Install the PyDDB package
pip install git+https://github.com/arup-group/PyDDB.git
# Quick Start
# Getting Data
Getting data from DDB is as simple as calling the appropriate method on the project object. This will handle pagination and return objects with metadata. Look at the API reference for more information.
Import the parts of the library you need:
from pyddb import DDB, env, Project, ParameterType
Instantiate the client and set the environment:
ddb = DDB(env.sandbox)
Get an existing ddb project:
my_project = Project.get_by_number(project_number="21515700")
Get data from the project:
project_parameters = my_project.get_parameters()
project_assets = my_project.get_assets()
project_sources = my_project.get_sources()
Get data from the taxonomy:
all_parameter_types = ParameterType.get_all()
some_parameter_types = ParameterType.search('area')
my_parameter_type = ParameterType.get("Plot area")
# Posting Data
Import the parts of the library you need:
from pyddb import DDB, env, Project, ParameterType, AssetType, Unit, Source, Parameter, Asset
Instantiate the client and set the environment:
ddb = DDB(env.sandbox)
Create a project or get an existing one:
project = Project.put("21515700")
Define the assets you want to post:
assets = [
my_site := Asset.create(
asset_type=AssetType.get("site"),
name="My DDB Site",
parent=None,
),
my_building := Asset.create(
asset_type=AssetType.get("building"),
name="My DDB Building",
parent=my_site,
)
]
Define the parameters you want to post:
parameters = [
Parameter.create(
parameter_type=ParameterType.get("Plot area"),
value=240,
unit=Unit.get("m²"),
parent=my_site,
source=Source.create(
source_type = SourceType.get("Derived Value"),
title="My source title",
reference="My source reference",
),
)
]
Post the data:
project.post_assets(assets)
project.post_parameters(parameters)
Each of these post methods will return what they are posting and do not add duplicate data. Posting new parameters will not override existing parameters if the value, unit, or source has not changed.
This means that these methods can be used to created automated workflows.
# API Reference
These docs are broken down by class and provide a description and example of how they should be used. In this library, we have two main types of classes that serve different purposes:
Data Classes: These represent the values of our data and are what you will be posting and retreiving to interact with your project data. These will typically be tied to the taxonomy classes so we know what kind of data we are working with.
Taxonomy Classes: These classes, such as 'ParameterType' or 'AssetType', are determined by our team. These are ways of categorising our data and giving it context.
In short, the data classes are the data you are working with, and the taxonomy classes are the ways we classify it. You can also explore our glossary for more information on the terms we use.
# Data Classes
The DDB and Project classes are the main entry points for the library.
The DDB
class is used in every script to instantiate the client and set the environment.
The DDB
class is used to access data across projects, while the Project
class is used to access data on a specific project.
# DDB
The DDB class is the main class for the client. It is used to instantiate the client and set the environment. All scripts should instantiate the client with the environment they are working in.
from pyddb import DDB, env
ddb_client = DDB(env.sandbox)
You can now use this to access data across projects. It's recommended to use very specific queries to reduce the amount of data returned.
all_projects = ddb_client.get_projects()
all_plot_areas = ddb_client.get_parameters(
parameter_type_id=[
ParameterType.get("Plot area").id
]
)
# Project
The Project class is used to access data on a specific project. It is instantiated by passing the project number to the get_by_number
or put
methods.
from pyddb import DDB, env, Project
DDB(env.sandbox)
my_project = Project.get_by_number("00000000")
my_project = Project.put("00000000")
The difference between these two methods is that get_by_number
will only return the project if it exists in DDB. put
will create the project if it does not exist.
# Source
The Source class is used to create and post sources to DDB. A new source is instantiated by passing the source type, title, and reference to the create
method.
from pyddb import DDB, env, Project, Source, SourceType
DDB(env.sandbox)
my_source = Source.create(
title="My source title",
reference="My source reference",
source_type=SourceType.get("Derived Value"),
)
Sources can be posted to DDB by passing a list of sources to the post_sources
method on a Project
.
my_project = Project.put("00000000")
my_project.post_sources([my_source])
Sources on a project can be retreived by calling the get_sources
method on a Project
.
project_sources = my_project.get_sources()
This means it's very easy to get sources from a past project and reuse them in a new project.
# Asset
The Asset
class is used to create and post assets to DDB. A new asset is instantiated by passing the name, asset type, and parent to the create
method.
One important thing to note is that in DDB, you cannot have two assets with the same name, type and parent. This means that a single site can't have two buildings with the same name. This library uses this logic to prevent duplicate assets from being created and instead will return the existing assets if scripts are rerun.
from pyddb import DDB, env, Project, Asset, AssetType
DDB(env.sandbox)
my_asset = Asset.create(
name="My asset name",
asset_type=AssetType.get("Site"),
)
my_other_asset = Asset.create(
name="My other asset name",
asset_type=AssetType.get("Building"),
parent=my_asset,
)
yet_another_asset = Asset.create(
asset_sub_type=AssetSubType.get("Cooling"),
parent=my_other_asset,
)
There are a few rules for creating assets:
- Assets need either an
AssetType
, or anAssetSubType
so we know what kind of asset it is. - Assets without an
AssetSubType
need a name. - Assets need to follow the Asset Type hierarchy.
For example, a
Building
asset needs to have aSite
asset as a parent.
You can explore the AssetType
class to get an understanding of the hierarchy or use the DDB UI.
Assets can be posted to DDB by passing a list of assets to the post_assets
method on a Project
.
my_project = Project.put("00000000")
my_project.post_assets([my_asset, my_other_asset, yet_another_asset])
Assets on a project can be retreived by calling the get_assets
method on a Project
.
project_assets = my_project.get_assets()
This means it's very easy to get assets from a past project and reuse them in a new project.
# Parameter
The Parameter
class is used to create and post parameters to DDB. A new parameter is instantiated by passing the parameter type, value, unit, source, and parent to the create
method.
You'll want to be familiar with the Source
and Asset
classes before posting parameters.
from pyddb import DDB, env, Project, Parameter, ParameterType, Source, SourceType, Asset, AssetType
DDB(env.sandbox)
my_source = Source.create(
title="My source title",
reference="My source reference",
source_type=SourceType.get("Derived Value"),
)
my_site = Asset.create(
name="My site name",
asset_type=AssetType.get("Site"),
)
my_parameter = Parameter.create(
parameter_type=ParameterType.get("Height"),
value=100,
unit="m",
source=my_source,
parent=my_site,
)
Parameters can be posted to DDB by passing a list of parameters to the post_parameters
method on a Project
.
my_project = Project.put("00000000")
my_project.post_parameters([my_parameter])
A Parameter
can be created with just a ParameterType
, which when posted, will create a blank parameter without any values.
When posted with a value, the library will check if the value, unit, or source has changed and update the parameter if it has.
We can also post comments alongside our parameters with values.
parameter_with_comment = Parameter.create(
parameter_type=ParameterType.get("Location"),
value="Glasgow, Scotland",
source=my_source,
comment="Includes the city of Glasgow and the surrounding area.",
)
# Taxonomy Classes
These classes represent the different ways we have classified our data.
As an example, we have thousands of Parameter
data classes in our projects, and these are all classified by a few ParameterType
taxonomy class.
This could be hundreds of Parameter
objects with the ParameterType
of "Area".
These classes are used to provide context to the data, give it consistent structure, and make it easier to work with.
All of these classes have 3 common methods that you will use to interact with them:
get_all
- This will return all of the taxonomy objects of that type in DDB.
search
- This will return a list of taxonomy objects that match the search term.
get
- This will return a single taxonomy object by name or uuid.
# Source Type
DDB has a taxonomy for source types. Examples of these include 'Assumption', 'Derived Value', and 'Industry Guidance'.
from pyddb import DDB, env, SourceType
DDB(env.sandbox)
all_source_types = SourceType.get_all()
my_source_type = SourceType.get("Derived Value")
my_source_type.id
my_source_type.name
# Asset Type
DDB has a taxonomy for asset types. Examples of these include 'Site', 'Building', 'Space', 'Bridge', 'Tunnel', and 'Road'.
from pyddb import DDB, env, AssetType
DDB(env.sandbox)
all_asset_types = AssetType.get_all()
my_asset_type = AssetType.get("Site")
my_asset_type.id
my_asset_type.name
Some asset types have subtypes. You can check their asset_sub_type
boolean property to see if they have subtypes.
if my_asset_type.asset_sub_type:
sub_types = my_asset_type.get_sub_types()
# Asset Subtype
The DDB taxonomy supports further classification of asset types into sub types. When an asset type has sub types, all instances of that type must be classified as one of the sub types.
As an example, the 'building system' AssetType
has several sub types, including 'Heating', 'Cooling', and 'Lighting'.
All assets of this type need to be one of those sub types.
from pyddb import DDB, env, AssetSubType
DDB(env.sandbox)
all_asset_sub_types = AssetSubType.get_all()
my_asset_sub_type = AssetSubType.get("Heating")
my_asset_sub_type.id
my_asset_sub_type.name
my_asset_sub_type.asset_type_id
# Parameter Type
DDB has a taxonomy for parameter types. Examples of these include 'Summer indoor dry bulb temperature', 'Modulus of elasticity', 'Date of last inspection'.
These are used to classify our Parameters
and apply some rules to them, such as the units they can have or the data types they can be.
from pyddb import DDB, env, ParameterType
DDB(env.sandbox)
all_parameter_types = ParameterType.get_all()
my_parameter_type = ParameterType.get("Modulus of elasticity")
my_parameter_type.id
my_parameter_type.name
my_parameter_type.data_type
my_parameter_type.unit_type
# Item Type
An ItemType
is a combination of an AssetType
, a ParameterType
, and potentially an AssetSubType
.
These are created by our team and make different ParameterTypes
available on different Assets
based on their AssetType
and AssetSubType
.
We can use the ItemType
class to see which ParameterTypes
are available for different AssetTypes
.
from pyddb import DDB, env, ItemType
DDB(env.sandbox)
all_item_types = ItemType.get_all()
site_item_types = ItemType.get_by_asset_type(AssetType.get("Site"))
ItemTypes
allow us to apply even more specific rules to our data, such as a list of available Options
.
For example, there could be an ItemType
that represents the ParameterType
'Field type' on the AssetType
'Site'.
This ItemType
could have a list of Options
that represent the different types of fields that can be selected, such as 'Brownfield' or 'Greenfield'.
This would mean that in DDB, all values of 'Field type' on 'Site' assets would have to be one of those two options.
for item_type in site_item_types:
if item_type.has_options:
options = item_type.get_options()
# Option
An Option
is a value that can be selected for a Parameter
of a certain ItemType
.
These are preset lists of values that are allowed for different ItemTypes
.
You can access their values through the 'value' property:
for option in options:
print(option.value)
# Unit Type
DDB has a taxonomy for unit types. Examples of these include 'Temperature', 'Length', 'Area', and 'Volume'.
These allow us to group up units that are similar, such as 'Celsius' and 'Fahrenheit' both being in the 'Temperature' unit type.
ParameterTypes
have a unit_type
property that is used to restrict the units that can be used for that ParameterType
.
from pyddb import DDB, env, UnitType
DDB(env.sandbox)
all_unit_types = UnitType.get_all()
my_unit_type = UnitType.get("Temperature")
my_unit_type.id
my_unit_type.name
You can also retrieve all units for a given UnitType
.
my_unit_type = UnitType.get('Length')
length_units = my_unit_unit.get_units()
# Unit System
DDB has a taxonomy for unit systems. Examples of these include 'SI', 'Imperial', and 'General'.
These allow us to further classify our units.
from pyddb import DDB, env, UnitSystem
DDB(env.sandbox)
all_unit_systems = UnitSystem.get_all()
my_unit_system = UnitSystem.get("General")
my_unit_system.id
my_unit_system.name
You can also retrieve all units for a given UnitSystem
.
my_unit_system = UnitSystem.get('General')
general_units = my_unit_unit.get_units()
# Unit
DDB has a taxonomy for units. Examples of these include 'm', 'kg', and 'ft'.
from pyddb import DDB, env, Unit
DDB(env.sandbox)
all_units = Unit.get_all()
my_unit = Unit.get("m")
my_unit.id
my_unit.symbol
# Role Type
Role types define different types of access to the DDB project. A single user can have multiple roles.
The different role types are:
Admin
- For modifying user permissions
Reader
- For reading project data
Editor
- For editing project data
Checker
- For updating QA status
Approver
- For approving data
RoleType
is an Enum
object.
from pyddb import DDB, env, Project, RoleType
DDB(env.sandbox)
project = Project.put('00000000')
project.put_user_roles([
(RoleType.Admin, '[email protected]'),
(RoleType.Editor, '[email protected]'),
])
# Tag
Tags
are used to organise our data and can be useful for filtering and searching.
Examples of these include 'Google', 'Scotland', and 'Mechanical'.
from pyddb import DDB, env, Tag, Project, Asset, Parameter
DDB(env.sandbox)
all_tags = Tag.get_all()
my_tag = Tag.get("Google")
project = Project.put('00000000')
some_asset = project.get_assets()[0]
some_parameter = some_asset.get_parameters()[0]
project.post_tags([my_tag])
some_asset.post_tags([my_tag])
some_parameter.post_tags([my_tag])
# Tag Type
Tags
are grouped together by their TagTypes
.
Examples of these include 'Client', 'Region', and 'Workstage'.
from pyddb import DDB, env, TagType
DDB(env.sandbox)
all_tag_types = TagType.get_all()
my_tag_type = TagType.get("Client")
client_tags = my_tag_type.get_tags()