Skip to content

Commit 3a3165b

Browse files
fix(aveva_pi): address PR review feedback — README template, code hardening
- README: add Features section, fix Getting started to match template (fivetran init code block + explanation + init docs link), add Additional files section, add PI AF asset hierarchy explanation, fix Data handling to say types are explicitly defined not auto-detected - connector.py: add base_url scheme validation (http/https check); remove internal ERD link from schema() comment - models.py: use hashlib.md5(usedforsecurity=False) for FIPS safety - sync.py: use .get() for Element.WebId to prevent KeyError on malformed PI Web API responses Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1f4c534 commit 3a3165b

4 files changed

Lines changed: 43 additions & 7 deletions

File tree

aveva_pi/README.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44

55
This connector syncs data from AVEVA PI (formerly OSIsoft PI) to your Fivetran destination. It communicates with the PI system via the PI Web API REST interface — no proprietary ODBC drivers are required, so the connector runs in Fivetran's managed cloud environment without any additional installation.
66

7+
The PI Asset Framework (PI AF) is AVEVA PI's asset hierarchy layer. It organizes physical assets (such as pumps, tanks, or compressors) as *elements*, each with typed *attributes* that can link to PI Point time-series tags. *Event frames* record time-bounded operational events (for example, alarms or batches) against elements in the hierarchy.
8+
79
Key capabilities:
810
- REST-based connectivity via PI Web API (HTTPS + Basic auth) — no ODBC driver installation required
911
- Four fixed tables: `elements`, `attributes`, `event_frames`, and `recorded_values`
1012
- Full reimport for `elements` and `attributes` (PI AF asset hierarchy)
1113
- Cursor-based incremental sync for `event_frames` and `recorded_values` with adaptive time-window backoff
1214
- 2-hour late-arrival rollback on `recorded_values` to capture values written after their timestamps
1315
- MD5-based synthetic `_fivetran_id` primary key for `recorded_values`
14-
- Authentication-aware retry: 4xx responses surface immediately; 5xx / network errors retry up to 3 times
16+
- Authentication-aware retry: 4xx responses surface immediately; 408/429 and 5xx / network errors retry up to 3 times
1517
- `recorded_values` sync is opt-in (set `sync_recorded_values = "true"`) because it can generate very large data volumes
1618

1719

@@ -30,7 +32,27 @@ Key capabilities:
3032

3133
Refer to the [Connector SDK Setup Guide](https://fivetran.com/docs/connectors/connector-sdk/setup-guide) to get started.
3234

33-
Running `fivetran init --template aveva_pi` creates a new Connector SDK project pre-populated with this connector's source files. You can then update `configuration.json` with your PI Web API credentials and run `fivetran debug` to test locally against your own PI server.
35+
To initialize a new Connector SDK project using this connector as a starting point, run:
36+
37+
```
38+
fivetran init --template aveva_pi
39+
```
40+
41+
`fivetran init` initializes a new Connector SDK project by setting up the project structure, configuration files, and a connector you can run immediately with `fivetran debug`. For more information on `fivetran init`, refer to the [Connector SDK `init` documentation](https://fivetran.com/docs/connector-sdk/connector-development-and-configuration/connector-sdk-commands#fivetraninit).
42+
43+
> Note: Ensure you have updated the `configuration.json` file with the necessary parameters before running `fivetran debug`. See the [Configuration file](#configuration-file) section for details on the required configuration parameters.
44+
45+
46+
## Features
47+
48+
- REST-based connectivity via PI Web API (HTTPS + Basic auth) — no ODBC driver installation required
49+
- Four fixed tables: `elements`, `attributes`, `event_frames`, and `recorded_values`
50+
- Full reimport for `elements` and `attributes` (PI AF asset hierarchy: assets, their attributes, and PI Point data references)
51+
- Cursor-based incremental sync for `event_frames` and `recorded_values` with adaptive time-window backoff
52+
- 2-hour late-arrival rollback on `recorded_values` to capture values written after their timestamps
53+
- Explicit column type definitions in `connector.py` (including `UTC_DATETIME` for all timestamp columns)
54+
- `recorded_values` sync is opt-in (set `sync_recorded_values = "true"`) because it can generate very large data volumes
55+
- Authentication-aware retry: 4xx responses surface immediately; 408/429 and 5xx / network errors retry up to 3 times with exponential backoff
3456

3557

3658
## Configuration file
@@ -81,7 +103,7 @@ Checkpointing occurs after each successful time window (incremental) or every 10
81103

82104
## Data handling
83105

84-
- Schema: Fixed four-table schema. Column types are auto-detected by the Fivetran Connector SDK from the data values upserted during sync.
106+
- Schema: Fixed four-table schema. Column types are explicitly defined in `connector.py` (for example, `UTC_DATETIME` for all timestamp columns and `BOOLEAN` for the `good` column).
85107
- Timestamps: PI Web API returns ISO 8601 timestamps. The connector parses them to UTC-aware `datetime` objects before yielding rows to Fivetran.
86108
- PI digital states: When a PI recorded value is a system digital state (a JSON object like `{"Name": "Shutdown", "Value": 248}`), only the `Name` string is stored in the `value` column.
87109
- Hash IDs: The `recorded_values` table has no natural primary key. A `_fivetran_id` column is generated as the MD5 hex digest of `attribute_web_id|timestamp`.
@@ -157,6 +179,13 @@ Incremental by `timestamp`. Opt-in via `sync_recorded_values = "true"`. Represen
157179
| `good` | BOOLEAN | |
158180

159181

182+
## Additional files
183+
184+
- `client.py` — HTTP session setup, authenticated API calls with retry/backoff, pagination via `Links.Next`, and AF database discovery.
185+
- `models.py` — Record extraction helpers that map raw PI Web API response dicts to flat table rows, plus timestamp parsing and MD5 primary key generation.
186+
- `sync.py` — Per-table sync strategies: full reimport for elements and attributes, cursor-based incremental sync with adaptive time-window backoff for event frames and recorded values.
187+
188+
160189
## Additional considerations
161190

162191
The examples provided are intended to help you effectively use Fivetran's Connector SDK. While we've tested the code, Fivetran cannot be held responsible for any unexpected or negative consequences that may arise from using these examples. For inquiries, please reach out to our Support team.

aveva_pi/connector.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ def validate_configuration(configuration: dict):
4747
if not configuration.get(key):
4848
raise ValueError(f"Missing or empty required configuration key: '{key}'")
4949

50+
# Validate base_url scheme to catch common mistakes early
51+
url_val = configuration.get("base_url", "")
52+
if not url_val.startswith(("http://", "https://")):
53+
raise ValueError(f"Invalid base_url '{url_val}'. Expected an http:// or https:// URL.")
54+
5055
# Validate start_date format if provided
5156
start_date = configuration.get("start_date")
5257
if start_date:
@@ -82,8 +87,6 @@ def schema(configuration: dict):
8287
validate_configuration(configuration)
8388

8489
# Four tables mapping directly to PI AF object types exposed by PI Web API.
85-
# Column definitions sourced from the AVEVA PI ERD:
86-
# https://docs.google.com/presentation/d/1Ksupz_9XokWkOKh5HN9lVCbiqAq93liKoL8c2H0-ovY/edit#slide=id.g2103cff6d9e_0_815
8790
return [
8891
{
8992
"table": "elements",

aveva_pi/models.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ def generate_recorded_value_id(attr_web_id: str, timestamp: str) -> str:
130130
Returns:
131131
A 32-character lowercase hex digest string.
132132
"""
133-
return hashlib.md5(f"{attr_web_id}|{timestamp}".encode("utf-8")).hexdigest()
133+
return hashlib.md5(
134+
f"{attr_web_id}|{timestamp}".encode("utf-8"), usedforsecurity=False
135+
).hexdigest()
134136

135137

136138
def extract_recorded_value(item: dict, attr_web_id: str) -> dict:

aveva_pi/sync.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,9 @@ def _process(item, element_web_id):
229229
params={"searchFullHierarchy": "true", "maxCount": __MAX_COUNT},
230230
):
231231
element_web_id = (
232-
item["Element"]["WebId"] if isinstance(item.get("Element"), dict) else ""
232+
item.get("Element", {}).get("WebId", "")
233+
if isinstance(item.get("Element"), dict)
234+
else ""
233235
)
234236
_process(item, element_web_id)
235237
except ValueError as exc:

0 commit comments

Comments
 (0)