JMP 19 將在 JMP 18 中引入的資料連接器功能的基礎上進行擴展,從而實現對更多資料來源的統一存取。除了新增更多內建連線類型外,JMP 19 還允許使用者透過公開的 Python API 定義自訂連線類型。與內建類型一樣,這些自訂連接類型也將受益於資料連接器功能,包括其組態管理和查詢建構器整合。此外,由於它們是用 Python 實現的,因此可以利用現有的 Python 庫。本次示範將介紹 API,探討其中涉及的概念,並展示其實際應用。
大家好。今天我來聊聊JMP的資料連接器框架,特別是JMP 19中如何使用Python對其進行擴充。在JMP 18中,我們引入了資料連接器框架,並用它來改進與ODBC的連接。在JMP 19中,我們將進一步擴展這項功能。
您可能在其他地方聽說過我們如何利用它重新連接到 SaaS,以及如何提供與 Snowflake 的原生連接。我們也使用我接下來要講的這個框架——這個 Python API——來實現與 Amazon S3 和 Microsoft SharePoint 的連接,這些連接不會與 JMP 捆綁在一起,而是透過 JMP Marketplace 分發。
本次演講我想涵蓋幾個面向。首先,我想簡要說明一下,如果您有自己的資料來源,為什麼要將其與 JMP 整合。如果您確實想要集成,那麼您需要了解它應該如何融入 JMP 的系統中,JMP 需要做什麼才能與您的資料來源通信,以及您需要提供哪些資訊才能實現這一目標。特別是,您需要編寫哪些程式碼。
最後,我們將討論如何共享程式碼。您編寫的程式碼對您自己有效,但公司裡的其他人可能也想使用它。您可能希望與世界各地的其他 JMP 使用者共用程式碼。為了簡化這個過程,我們選擇了 Python,部分原因是 Python 擁有豐富的函式庫,或者您可能已經編寫過相關的程式碼。我們就是這樣實現了與 Amazon S3 和 Microsoft SharePoint 的連線。我們能夠封裝現有的 Python 函式庫,希望這也能讓您更輕鬆地使用程式碼。
除了我們力求簡化整合流程之外,您可能想要整合的另一個原因是它能夠幫助您管理配置。使用者可以使用連接器編輯器和查詢建構器來建立和編輯描述連接的配置,從而以圖形化的方式建立查詢、篩選資料、選擇列等等。最後,您也可以進行協作。您建立的連接和配置可以透過插件匯出和共享。
如果您之前沒見過這項功能,稍後我會示範其實際應用,您就會更清楚地了解它。但在那之前,我們先來談談各個組件之間的關係,以及它們是如何協同工作的。首先,您需要找到資料來源,並讓它與 JMP 通訊。
但是,在 JMP 中,我們所說的資料指的是資料表。有些資料表可以直接從資料來源獲取,有些則需要一些額外的處理。例如,您可能有一個 Web 端點,它會傳回這種層級結構的 JSON 資料。在這種情況下,就需要進行一些處理。但只要它能夠以資料表的形式提供給 JMP,就可以被使用。
查詢建構器在存取這些資料時,會將其視為傳統資料庫,例如 MySQL 或 SQL Server。它假定資料已組織成命名表,並且這些表可能進一步組織成命名模式。
但您可能擁有不只一個資料來源。即使只有一個資料來源,也至少有多種存取方式。使用者可能擁有不同的憑證。或者您可能只是不想硬編碼資料地址。總而言之,您很可能需要一些選項。透過為這些選項提供特定值,您可以獲得一個配置。然後,使用該配置,您可以建立資料來源。這些配置都可以使用資料連接器編輯器建立。
以上就是簡要概述。但它如何轉化為程式碼呢?我們來看看。我現在在 JMP 裡。我們先把這個視窗移開,重新開始。左邊這段程式碼實作了幾種資料連接器類型。類型可以是像 ODBC 這樣的 JMP 內建類型。它也可以是 SaaS 類型,例如本地、遠端或透過網路連接。它還可以是 Snowflake。但無論如何,對於任何特定的類型,你可以選擇使用 Python 來實現。
這裡頂部有一些導入語句。特別是,我們導入了內建的 JMP 模組。這裡還有一個實用函數,可以用來確保我們能夠看到函數何時被呼叫。在這個演示中,之所以使用它,是因為 JMP 會呼叫程式碼。我們不會頻繁地呼叫程式碼。但為了理解程式運行的機制,追蹤函數呼叫的時機非常重要。
下面的程式碼才是真正實現這個類型的部分。在深入講解之前,我們先來看看我手邊的參考資料。這個清單中的各種元素在許多方面都與我剛才展示的圖表相對應。我們需要一些選項。我們將透過繼承 jmp.DataConnectorType 類,並定義一個欄位屬性來定義這些選項。我們需要將配置資訊轉換為資料來源。我們將繼承 jmp.DataConnector 類,並定義一個名為 _do_as_data_source 的函數來執行這種轉換。
最後,我們來看看資料來源本身。這是另一個子類別。我們將定義一些函數,用於了解是否存在模式、有哪些表格以及特定表格的資料內容。雖然我們不會在本演示中看到,但需要記住一點:如果您有一些資源不會自動清理,例如,如果您有一些連接本身沒有這種行為,那麼您需要找到 Python 的特殊雙下劃線方法,以便在不再引用這些資源時進行清理。因為 JMP 會將清理工作交給 Python 來處理關閉資料來源的情況。
考慮到這一點,我們再來看一下程式碼。我們看到 SimpleFolderConnectorType,它是 jmp.DataConnectorType 的子類別。正如前面提到的,它有一些字段,每個字段都有相應的選項。這裡第一個也是唯一一個選項是「Folder」。
在這種情況下,我們所說的資料夾,或者說我們的 SimpleFolder 連接,實際上就是我們的資料來源。它對應於我本地文件系統上的一個資料夾,該資料夾將以其中的文件形式公開資料。
首先,我們來定義一個名為「Folder」的元素,它在這個描述選項的字典中的值是字串類型,即 `str | None`。這意味著我們可以設定一個可選的字串值。接下來是配置類別 `SimpleFolderConnector`,它是 `jmp.DataConnector` 的子類別。它定義了 `_do_as_data_source` 函數,該函數會根據我們的配置進行資料轉換(或連接)。
我們可以像使用內建字典一樣,透過索引本身來存取配置值「資料夾」。我們會檢查它是否為空(即使用者是否省略了這個預設值),如果是,則拋出錯誤來告知使用者這是必需的。
最後,我們將它轉換為資料來源。資料來源類別是清單中的最後一個。當然,我們必須能夠創建它。我們定義一個初始化函數。在這個例子中,我們檢查它是否確實是一個目錄,因為我們想要做的是讓使用者(包括你自己或任何使用程式碼的人)知道是否有錯誤。建立資料來源時,應該同時進行連線。在本例中,實際上不需要進行任何連接,但我們至少可以檢查資料夾是否存在。
接下來要介紹的是取得模式(get-schemas)。在這種情況下,我們所說的資料來源沒有模式,可以直接存取表。因此,我們無需定義獲取模式,JMP 會自動識別出我們的資料來源未使用模式。
但我們必須先定義這些表。雖然這裡需要傳入一個模式參數,但我們會忽略它,因為我們不會用到這些表。不過,既然我們要查看文件,就需要為這些文件命名。這裡就直接使用檔名。我們會遍歷資料夾,並傳回其中每個檔案的名稱。
最後,我們需要取得數據表。這就是最後一個開啟表函數的作用。首先,我們需要重建包含資料的檔案路徑。我們將使用表名(即檔案名稱加上資料夾後綴),然後將路徑作為字串傳回。
我剛才提到的模型需要回傳一個資料表,你可以這樣做。如果你有一個像我之前舉例的 Web 端點,它會傳回一個層級結構的 JSON 或其他格式的資料。你可能需要自己將其轉換為數據表或直接返回。
但在這個例子中,由於文件僅來自文件系統,而我們在創建 S3 和 SharePoint 加載項時也遇到過這種情況,而且我認為我們也聽到用戶對訪問其他類似文件的數據存儲方式的興趣,因此,我們無需直接打開文件,只需傳遞這個字符串即可。然後 JMP 會為我們開啟檔案。
它會處理諸如確保文件以私密方式打開之類的事情。這裡回傳的資料表是一個實作細節,不應該直接暴露給使用者。使用查詢建構器時,您不會看到中間表,只會看到最終結果。在這種情況下,JMP 會為我們處理這些。這樣做還可以讓 JMP 保留打開表時可能涉及的任何設定。稍後我會詳細討論這一點。
最後,我們來看看這裡的呼叫:函數 jmp.DataConnector.tie。它的作用是接收兩個參數:類型類和連接器類,並確保它們彼此關聯。目前,它們之間沒有互相引用,但頂部的 DataConnectorType 類別是 JMP 用作存取點的。類型是一個包羅萬象的概念,它包含了構成它們連結的所有程式碼和定義。它們需要彼此關聯。
最後,我在這裡加一條註釋,說明最終的類型字串是什麼。如果是 DBC,那就是 ODBC。也可能是 SAS Viya 或 SAS Remote。在本例中,對於 Python,我們用 Python 冒號表示這是一個函數型別。 `__main__` 是模組的名稱,因為我們直接從 JMP 執行了它。最後,`SimpleFolderConnectorType` 是這個類別的名稱。
我們來看個實際操作。如果我開啟「資料連接器」對話框,建立一個新的連接,就會開啟對應的「資料連接器編輯器」。如果我們把型別改成 Python,就可以在這裡輸入剩餘的字串。我們把這些內容填進去。
完成上述操作後,我們可以看到“資料夾”選項。可以看到它沒有值,也就是 None。所以,如果我嘗試測試一下(我們嘗試連接),呼叫這個函數和這個函數,就會看到這裡的錯誤訊息:「資料夾是必需的」。
現在,為了實際存取一些數據,我將使用 JMP 自帶的範例導入數據。就是這個資料夾。我要複製一下。再顯示一遍。我們複製路徑,然後在 JMP 中輸入,並貼上到這裡。我們看到了測試結果。現在一切正常,所以我們繼續連線。
現在連接成功了,您可以看到這裡列出了一些表。實際上,這些表對應於我們這裡的文件。我們在這裡看到了 Animals_L.txt,在這裡又看到了它。查看日誌,我們可以看到使用日誌條目函數非常有效。我們可以看到,在測試過程中,我們多次呼叫了 _do_as_data_source 函數,也多次呼叫了資料來源的初始化函數。我們也看到 get-tables 函數被調用,它再次提供了這個清單。
如果我在這裡呼叫查詢建構器,我們就可以看到我們可以存取哪些列。可以看到,如果我們繼續建立查詢,就可以新增這些列。我們還可以進行一些篩選。例如,我們可以只選擇其中兩個季節。如果我們查看 SQL 語句,可以看到它包含了所有這些資訊。運行查詢,我們就得到了結果。
你可能想知道這一切是如何運作的。我們建立了一個 SQL 查詢,並執行了 SQL 程式碼。然而,在我們之前定義這些內容時,幾乎沒有 SQL 的概念。它之所以能夠運行,是因為我們提供了打開表的調用,提供了獲取資料的方法。然後 JMP 會幫我們完成剩下的工作。它使用 SQLite,一種底層嵌入式資料庫,以便暴露我們擁有的資料並運行 SQL 程式碼。例如,那個 WHERE 子句可以讓我們只看到其中兩個季節的資料。
讓我們來看看更複雜的文件。或許我們可以回到我們最愛的那個大類文件,也就是這裡Excel裡的文件。再次開啟查詢產生器。我們看到,在實際操作之前,會先彈出這個對話框。我們認為,如果文件(聽不清楚 00:17:43),就必須提示使用者才能指定文件內容。當JMP嘗試開啟這個檔案時,它會提示你填寫匯入精靈所需的資訊。
一切看起來都沒問題,點選「導入」。現在您可以看到,和之前一樣,我可以看到列名,也可以看到快照。我現在就點選「導入」。我們看到數據已經導入了。現在,您可能想知道,我每次都要這樣做嗎?答案是:不用。
我們來看看這個來源腳本。在頂部,我們可以看到預期的內容。這裡有「新建 SQL 查詢」。這裡還有連接訊息,在本例中是由數據連接器提供的。可以看到它引用了我們的類型,以及我們指定的資料夾。
但當我們查看表格時,會發現這裡有一個新選項:「開啟設定」。 「開啟設定」會擷取匯入精靈中指定的設定。具體來說,它會獲取底層大型類別表來源腳本中傳遞給「開啟」命令的額外參數,並將其提供在此。
這樣,如果我繼續運行,再次執行,你看,所有內容都已打開,一切正常。如果我們對設定做了非標準操作,例如需要從後面的列開始,那麼這些操作仍然會反映在這裡。從日誌中我們可以看到,整個過程又重新開始了。我們可以看到,我們再次呼叫了 `_do_as_data_source` 函數。然後我們獲取了表列表,並再次打開了這個表。
以上是基本操作。這個看起來稍微複雜一點。但它不僅更複雜,功能也更齊全。這再次表明,這是你能做的最基本、最小的配置,能夠建立連結。
現在看起來更高級了。我們將不再使用簡單的資料夾連接器,而是一個功能齊全的資料夾連接器。你看,字段定義要複雜得多,但它仍然是由與之前類似的構建模組組成的。
首先,這裡新增的是 `jmp.DataConnectoGroupedFields` 的使用。它的作用是允許我們指定字段所屬的不同群組。這會影響它們在資料連接器編輯器中的顯示方式。對於 ODBC,選項會被分組,我們可以在 Python 中使用相同的功能。
每個群組都指定此清單中的一對值,這些值將傳遞給 `jmp.DataConnectorGroupedFields`。我們有名稱,還有一個字典,類似於先前的字典,其中包含選項名稱和欄位定義。在欄位定義中仍然可以看到類型,即 `str | None`。但我們還將其包裝在 `jmp.DataConnectorField` 中,我給它起了個別名,因為我們多次使用它,而且它有點長,目的是為了指定附加信息,例如,當您將滑鼠懸停在選項名稱上時,將在數據連接器編輯器中顯示的工具提示。
您可以指定預設值。您也可以指定一個名稱,該名稱將顯示在資料連接器編輯器中,並且與將在 JSL 設定檔中使用的名稱不同。例如,我們在 JMP 中使用 ODBC 時就使用了此功能,以便本地化選項名稱或提供比預設名稱更友善的字串,因為預設情況下,這些字串用於 JSL。因此,我們需要一個 JSL 風格的名稱。
這裡的“Ordner”我認為是德語中“資料夾”的意思。我們稍後會看到。至於資料來源的檢視方式有何變化,它仍然基於資料夾,但我們也會選擇性地支援模式。在這種情況下,我們可以選擇不以先前的方式查看數據,而是採用一種新的方式:我們將此處提供的資料夾的子資料夾視為模式,然後將這些子資料夾中的檔案視為資料表。
我們將提供另一種選擇,透過限制為某些擴展名並進行裁剪,來改變我們查看從表名到底層檔案的映射的方式。
連接時,我們將資料轉換為資料來源的過程類似,但稍微複雜一些,因為我們有更多選項。我們仍然會勾選“資料夾”,但還會勾選副檔名選項。我們會拋出一個錯誤,然後將所有資料傳遞給新的資料來源。
我們的新資料來源會先檢查資料夾,並設定其他選項。它會提供模式。如果我們使用這些模式,那麼程式碼會類似這樣,遍歷資料夾下的每個子資料夾。任何子資料夾的目錄都會被取得其名稱,並將其用作模式的名稱。
但是,如果我們不使用模式,如果我們沒有設定新的進階選項,那麼我們將直接返回 None。之前我們沒有定義模式,也沒有使用 get-schemas,但這裡我們不能選擇性地定義一個函數,所以現在允許你回傳 None 來表示「我沒有任何模式」。我們沒有使用模式。
Get-tables 函數的想法與之前類似。最終目標是取得一系列文件的名稱。但由於選項眾多,我們可以採用幾種不同的方式來實現。首先,我們會取得目錄。具體方式取決於資料庫模式。實際上,這取決於我們是否使用了資料庫模式。然後,我們會先篩選出文件,再取得文件名稱;如果需要處理文件副檔名,則會採用更複雜的方法。
最後,為了取得資料表,我們反向執行了 get-schemas 和 get-tables 的過程。我們需要重新建構路徑。我們可能會把檔案副檔名加回去。但這無法取得相關的目錄,所以我們會在再次將其作為路徑返回之前,把檔案名稱加迴路徑末尾…再次將路徑作為字串返回。我們把它連接起來,然後就得到了之前的類型名稱。
我們來看看。我先滾動一下回到這裡,方便我們參考。和之前一樣,我們在這裡設定類型。可以看到,這裡有我們自訂的名稱和工具提示。資料夾方面,我們和之前一樣。我們來獲取它的路徑。實際上,我們再往上找一個。我們使用架構功能。我們複製一下。
那我們可以說我們確實想使用子資料夾作為架構。既然我們添加了這個高級選項,那就用它吧。確保一切看起來都沒問題。嗯,連結成功了。讓我們確保所有視窗都位於最前面最顯眼的位置。
現在我們在左側看到模式,它們實際上只是資料夾名稱。這是我們導入的數據。可以看到我們已將其限制為 .txt 檔案。像之前一樣,以動物為例,打開它。數據已導入。一切正常。這裡也像之前一樣有呼叫。如果我們查看日誌,可以看到呼叫了 get-schemas、get-tables、我們的實用函數以及 open-table。
你已經做了這麼多工作來創建這些類型。現在你想與他人分享。這是我們最後要討論的內容,也就是如何做到這一點。我還有另一份清單。
你已經編寫了 Python 程式碼,但根據你的編寫方式,你可能需要將其組織成一個套件。組織成包的目的是確保所有程式碼集中在一起,並且可以使用像 pip 這樣的工具進行安裝,或者在本例中,使用 JMP 封裝的 jpip,這樣你就可以在安裝時指定依賴項。例如,對於 S3,我們依賴 PyPI 上的 Boto3 包,使用套件提供了一種非常自然的方式來指定這種依賴關係。
你需要建立基礎配置。記得之前我直接在資料連接器編輯器中填寫類型名稱嗎?這樣做當然沒問題,但如果我能直接從配置清單中找到合適的配置就方便多了。這樣我就可以根據清單中的配置建立新的配置,而不用記住完整的名稱了。
你可能需要編寫一個安裝腳本。我們有一個很棒的 Python 包,但我們需要把它放在一個真正可以使用的地方。如果能自動完成,那就太好了。
最後,我們希望把所有功能整合到一個插件裡。插件安裝起來更方便,也更容易透過應用程式市場分發。總之,所有功能都集中在一個軟體包裡。
讓我們來看看它長什麼樣子。我這裡有一個插件,其中包含了我們今天早些時候看到的那個程式碼的進一步開發版本。我已經把它解壓出來了,這樣我們就能看到裡面都有些什麼。我記得清單裡的第一個東西是 Python 套件。我已經把它創建好了。我們有了 Python 程式碼,但它被組織成一個 Python 專案…抱歉,是 Python 套件。我們有 pyproject.toml 檔案。今天我實在沒時間詳細講解如何建構這樣的項目,不過網路上有很多相關的資源。
我們有一個「數據連接器」資料夾。這裡存放著插件的配置訊息,在本例中是基本配置。例如,如果我查看其中一個配置,會發現它只有一行,用於指定類型。它沒有指定其他配置,但為使用者提供了一個可以指向的參考點。然後,在本例中,我們還有 addinLoad,它包含安裝腳本。
當然,您可以嘗試直接創建所有這些,但您也可以使用 JMP 的插件創建工具。依序點擊“檔案”、“新建”、“插件”,然後我可以…我可以輸入名稱。我可以輸入 ID。我可以指定最低 JMP 版本,在本例中應該是 19。啟動腳本部分用於放置安裝該軟體包的腳本。我們希望確保它只安裝一次。
我們已經有了要新增的文件。在這個例子中,它們已經解壓縮在這裡了,但如果您是第一次自己創建,這些文件可能放在其他地方。我們可以新增軟體包。你看,所有東西都在這裡了。我們可以新增資料連接器。如果我們有下標,那麼我們就可以真正保存並創建我們自己的插件了。一旦所有東西都準備就緒,創建過程應該就非常簡單了。
這就是我想在這裡展示的全部內容。我們來總結一下。首先,我想談談為什麼要進行整合。正如我一開始所講的,資料連接器框架在編輯配置、共用配置以及使用查詢建構器互動式地建立查詢方面提供了許多功能。這就為封裝資料來源提供了一個很好的理由。這就是它的作用所在。我們之前討論過,在 JMP 中,一切都是資料表,而我們希望讓它看起來像一個資料庫。
要編寫哪些程式碼?我們講解了需要定義的各種子類別、需要編寫的各種函數以及如何指定選項。最後,我們討論瞭如何建立插件,以及如何與他人分享您的程式碼或將其發佈到 JMP Marketplace。
從某種程度上說,這算是對所有可能性的相對快速的概述。你們中的一些人可能會認真做筆記,並截取大量的螢幕截圖或照片,但如果你和我一樣,那麼你可能還需要一些其他的參考資料。好消息是,我們已經有了這些訊息,而且我們還在不斷創建更多資訊。
我今天展示的 API 目前已有文件。您可以在「資料存取」部分的 EA 說明中找到它。您需要確保取得的是最新版本,但如果 EA 沒有更新,則最新版本可能並非目前版本。我們正在添加一些文檔,這些文檔將位於腳本索引中。雖然它無法提供像 EA 說明中那樣全面的概述,但希望能作為 API 中各個功能的參考。
最後,我之前展示的那個插件,我們正在進行一些開發,並將把它發佈到 JMP Marketplace 上。我們希望它能成為一個資源,既展示 API,也展示如何創建 Python 包,以及如何將其發佈為插件。
我們會再次展示所有文檔,包括你今天看到的所有內容,還會展示一些我沒時間講解的更高級但可能仍然有用的功能。一定要看。
最後,我想談談我們未來的發展方向。在 JMP 20 及更高版本中,我們正在考慮對該 API 進行一些擴充。例如,您可能已經注意到,為了處理您的數據,JMP 實際上會將所有資料(即整個資料表)匯入本機,因為這是您需要從 `opentable` 函數傳回的內容。然後,正如我之前提到的,我們會透過 SQLite 在本地使用它。
但如果部分處理工作可以在伺服器端完成,而無需將所有內容下載到本地,那麼我們希望支援這種方式。這也是我們正在研究的方向,看看如何提供類似的功能。
好了,我想我們今天的分享就到這裡。謝謝聆聽。我希望您能運用今天所學的知識,建立新的資料來源,並將其封裝到這個資料連接器框架中。這樣,您就可以將這些資料來源發佈為插件,從而更輕鬆、更有效率地處理數據,並將其匯入 JMP。再次感謝。就這些。