From 54b0da40466cf62a5dd055ff753db1687b722149 Mon Sep 17 00:00:00 2001 From: zilto Date: Mon, 1 Apr 2024 16:41:34 -0400 Subject: [PATCH 1/3] jaffle shop example added --- examples/jaffleshop/LICENSE | 201 ++++++++++++++++++ examples/jaffleshop/README.md | 41 ++++ examples/jaffleshop/all_functions.png | Bin 0 -> 83178 bytes examples/jaffleshop/dataflows/__init__.py | 0 .../jaffleshop/dataflows/customer_flow.py | 49 +++++ examples/jaffleshop/dataflows/order_flow.py | 51 +++++ examples/jaffleshop/dataflows/staging.py | 47 ++++ examples/jaffleshop/requirements.txt | 2 + examples/jaffleshop/run.py | 35 +++ 9 files changed, 426 insertions(+) create mode 100644 examples/jaffleshop/LICENSE create mode 100644 examples/jaffleshop/README.md create mode 100644 examples/jaffleshop/all_functions.png create mode 100644 examples/jaffleshop/dataflows/__init__.py create mode 100644 examples/jaffleshop/dataflows/customer_flow.py create mode 100644 examples/jaffleshop/dataflows/order_flow.py create mode 100644 examples/jaffleshop/dataflows/staging.py create mode 100644 examples/jaffleshop/requirements.txt create mode 100644 examples/jaffleshop/run.py diff --git a/examples/jaffleshop/LICENSE b/examples/jaffleshop/LICENSE new file mode 100644 index 000000000..8dada3eda --- /dev/null +++ b/examples/jaffleshop/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/examples/jaffleshop/README.md b/examples/jaffleshop/README.md new file mode 100644 index 000000000..34deccc7a --- /dev/null +++ b/examples/jaffleshop/README.md @@ -0,0 +1,41 @@ +# Jaffle shop + +This repository is a reimplementation of the canonical [dbt example jaffle_shop](https://github.com/dbt-labs/jaffle_shop). It illustrates data transformations for an ecommerce store. + +Data transformations are implemented using the Python library [Ibis](https://ibis-project.org/) which allows to define SQL operations that works across backends. By default, it uses [duckdb](https://duckdb.org/) for local development. Hamilton + Ibis provides a Python-centric alternative to dbt ([Learn more](https://hamilton.dagworks.io/en/latest/integrations/ibis/)). + +## Content +The content and structure aims to match the [original dbt `jaffle_shop`](https://github.com/dbt-labs/jaffle_shop/tree/main) example. On the other hand, Hamilton is just a Python library and is flexible regarding project structure. + +- `data/`: samples data; equivalent to `seeds/` in the dbt repo. +- `dataflows/staging` load raw data and rename columns to avoid naming conflicts; equivalent to `models/staging/` in the dbt repo. +- `dataflows/customer_flow.py` and `dataflows/order_flow.py` define data transformations; equivalent to `models/customers.sql` and `models/orders.sql` in the dbt repo. +- `run.py` specify where to load data from and how to execute dataflows. + + Genrally, you'll notice Hamilton aims to reduce the sprawl of configurations (`.yaml`) and documentation (`.md`). Instead, it uses docstrings, type hints, or Python object to couple them to with your code (`.py`). + +## Set up +1. create and activate virtual environment + + ```script + python -m venv venv & . venv/bin/activate + ``` +2. install requirements + + ```script + pip install -r requirements.txt + ``` + +3. execute the Hamilton dataflow + + ```script + python run.py + ``` + +## Resources +Jaffle shop is an example used by many different frameworks, which can ground comparisons between each other. + +- dbt: https://github.com/dbt-labs/jaffle_shop/blob/main/models/orders.sql +- dbt + duckdb: https://github.com/dbt-labs/jaffle_shop_duckdb +- Kedro: https://github.com/deepyaman/jaffle-shop/blob/main/src/jaffle_shop/pipelines/data_processing/nodes.py +- Dagster: https://github.com/stkbailey/dagster-jaffle-shop diff --git a/examples/jaffleshop/all_functions.png b/examples/jaffleshop/all_functions.png new file mode 100644 index 0000000000000000000000000000000000000000..63d566140ec039cd1500551885f1a0dbf4eec390 GIT binary patch literal 83178 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

xL|ujri%i^d#vEK!KgYa5|K-xCHdXEI85Fo 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 D0brjt literal 0 HcmV?d00001 diff --git a/examples/jaffleshop/dataflows/__init__.py b/examples/jaffleshop/dataflows/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/jaffleshop/dataflows/customer_flow.py b/examples/jaffleshop/dataflows/customer_flow.py new file mode 100644 index 000000000..1600cf890 --- /dev/null +++ b/examples/jaffleshop/dataflows/customer_flow.py @@ -0,0 +1,49 @@ +import ibis +import ibis.expr.types as ir + + +def customer_orders(orders: ir.Table) -> ir.Table: + """Aggregate statistics about previous customer orders""" + return orders.group_by("customer_id").aggregate( + first_order=orders.order_date.min(), + most_recent_order=orders.order_date.max(), + number_of_orders=orders.order_id.count(), + ) + + +def customer_payments(orders: ir.Table, payments: ir.Table) -> ir.Table: + """Customer order and payment info""" + return ( + payments.left_join(orders, "order_id") + .group_by(orders.customer_id) + .aggregate(total_amount=ibis._.amount.sum()) + ) + + +def customers_final( + customers: ir.Table, customer_orders: ir.Table, customer_payments: ir.Table +) -> ir.Table: + """This table has basic information about a customer, as well as + some derived facts based on a customer's orders + + customer_id: This is a unique identifier for a customer + first_name: Customer's first name. PII. + last_name: Customer's last name. PII. + first_order: Date (UTC) of a customer's first order + most_recent_order: Date (UTC) of a customer's most recent order + number_of_orders: Count of the number of orders a customer has placed + total_order_amount: Total value (AUD) of a customer's orders + """ + return ( + customers.left_join(customer_orders, "customer_id") + .drop("customer_id_right") + .left_join(customer_payments, "customer_id")[ + "customer_id", + "first_name", + "last_name", + "first_order", + "most_recent_order", + "number_of_orders", + ibis._.total_amount.name("customer_lifetime_value"), + ] + ) diff --git a/examples/jaffleshop/dataflows/order_flow.py b/examples/jaffleshop/dataflows/order_flow.py new file mode 100644 index 000000000..896bba43c --- /dev/null +++ b/examples/jaffleshop/dataflows/order_flow.py @@ -0,0 +1,51 @@ +import ibis +import ibis.expr.types as ir + + +def total_amount_by_payment_method(payments: ir.Table) -> dict: + """Create a column for each payment method indicating total amount spent""" + return { + f"{method}_amount": ibis.coalesce( + payments.amount.sum(where=payments.payment_method == method), 0 + ) + for method in ["credit_card", "coupon", "bank_transfer", "gift_card"] + } + + +def order_payments( + payments: ir.Table, + total_amount_by_payment_method: dict, +) -> ir.Table: + """Aggregate total amount per order and decompose by payment method""" + return payments.group_by("order_id").aggregate( + **total_amount_by_payment_method, total_amount=payments.amount.sum() + ) + + +def orders_final(orders: ir.Table, order_payments: ir.Table) -> ir.Table: + """This table has basic information about orders, + as well as some derived facts based on payments + + order_id: This is a unique identifier for an order + customer_id: Foreign key to the customers table + order_date: Date (UTC) that the order was placed + status: Orders can be one of the following statuses: + - placed: The order has been placed but has not yet left the warehouse + - shipped: The order has ben shipped to the customer and is currently in transit + - completed: The order has been received by the customer + - return_pending: The customer has indicated that they would like to return the order, + but it has not yet been received at the warehouse + - returned: The order has been returned by the customer and received at the warehouse + amount: Total amount (AUD) of the order + credit_card_amount: Amount of the order (AUD) paid for by credit card + coupon_amount: Amount of the order (AUD) paid for by coupon + bank_transfer_amount: Amount of the order (AUD) paid for by bank transfer + gift_card_amount: Amount of the order (AUD) paid for by gift card + """ + return ( + orders.left_join(order_payments, "order_id") + .select( + "order_id", "customer_id", "order_date", "status", ibis.selectors.contains("amount") + ) + .rename(amount="total_amount") + ) diff --git a/examples/jaffleshop/dataflows/staging.py b/examples/jaffleshop/dataflows/staging.py new file mode 100644 index 000000000..e1b663a1e --- /dev/null +++ b/examples/jaffleshop/dataflows/staging.py @@ -0,0 +1,47 @@ +import ibis +import ibis.expr.types as ir + + +def customers(connection: ibis.BaseBackend, customers_source: str) -> ir.Table: + """raw data about customers + + columns: + - customer_id + - first_name + - last_name + """ + return connection.read_parquet(customers_source, table_name="customers")[ + "id", "first_name", "last_name" + ].rename(customer_id="id") + + +def orders(connection: ibis.BaseBackend, orders_source: str) -> ir.Table: + """raw data about orders + + columns: + - order_id + - customer_id + - order_date + - status + """ + return connection.read_parquet(orders_source, table_name="orders")[ + "id", "user_id", "order_date", "status" + ].rename(order_id="id", customer_id="user_id") + + +def payments(connection: ibis.BaseBackend, payments_source: str) -> ir.Table: + """raw data about payments; convert amount from cents to dollars + + columns: + - payment_id + - order_id + - payment_method + - amount + """ + return ( + connection.read_parquet(payments_source, table_name="payments")[ + "id", "order_id", "payment_method", "amount" + ] + .rename(payment_id="id") + .mutate(amount=ibis._.amount / 100) + ) diff --git a/examples/jaffleshop/requirements.txt b/examples/jaffleshop/requirements.txt new file mode 100644 index 000000000..d81448183 --- /dev/null +++ b/examples/jaffleshop/requirements.txt @@ -0,0 +1,2 @@ +ibis-framework[duckdb] +sf-hamilton[visualization] diff --git a/examples/jaffleshop/run.py b/examples/jaffleshop/run.py new file mode 100644 index 000000000..4346648d5 --- /dev/null +++ b/examples/jaffleshop/run.py @@ -0,0 +1,35 @@ +import ibis + +# import dataflow modules +from dataflows import customer_flow, order_flow, staging + +from hamilton import driver + + +def main(): + # build Driver with dataflow modules + dr = driver.Builder().with_modules(staging, customer_flow, order_flow).build() + # create a visualization of the full dataflow + dr.display_all_functions("all_functions.png") + + duckdb_connection = ibis.duckdb.connect("jaffleshop.duckdb") + inputs = dict( + connection=duckdb_connection, + customers_source="data/raw_customers.parquet", + orders_source="data/raw_orders.parquet", + payments_source="data/raw_payments.parquet", + ) + + # results is a dictionary containing the Ibis expression, i.e., query plans + outputs = dr.execute(["orders_final", "customers_final"], inputs=inputs) + + # execute the `orders_final` ibis expression to return a dataframe + df = outputs["orders_final"].to_pandas() + print(df.head()) + + # execute the `customers_final` ibis expression to create a duckdb table + duckdb_connection.execute(outputs["customers_final"]) + + +if __name__ == "__main__": + main() From d37222a96050ed0469329333ca72dbe97c6de431 Mon Sep 17 00:00:00 2001 From: zilto Date: Mon, 1 Apr 2024 16:44:01 -0400 Subject: [PATCH 2/3] data files added --- examples/jaffleshop/data/raw_customers.parquet | Bin 0 -> 3773 bytes examples/jaffleshop/data/raw_orders.parquet | Bin 0 -> 4433 bytes examples/jaffleshop/data/raw_payments.parquet | Bin 0 -> 4548 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/jaffleshop/data/raw_customers.parquet create mode 100644 examples/jaffleshop/data/raw_orders.parquet create mode 100644 examples/jaffleshop/data/raw_payments.parquet diff --git a/examples/jaffleshop/data/raw_customers.parquet b/examples/jaffleshop/data/raw_customers.parquet new file mode 100644 index 0000000000000000000000000000000000000000..13510121eb0ba42a764ea3b2c35ad47e16cd4661 GIT binary patch literal 3773 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 AIRF3v literal 0 HcmV?d00001 diff --git a/examples/jaffleshop/data/raw_orders.parquet b/examples/jaffleshop/data/raw_orders.parquet new file mode 100644 index 0000000000000000000000000000000000000000..923870f22ef27f972d08974448c925c7516b71be GIT binary patch literal 4433 zcmchbdu&tJ9mlT|V<#cJxyA+q!2^?aNorr)0W+XnCnUD(m?Q+hfTq5WBu=kyTtAW+ z+A2t!ggV3!s}P&e)>T`_9%Z!ZDkhOlio3L3#OupF!aE5RzT8ms|p0Sjzk9k2rjSPwP;C)fx!fz6-^YymFt zC9oB218%S#G=m4ggW$`c1+;=T@DT6-FW`U=_(1@)gB@Te2!dVUD_}R+1NMT4!9Ku) z5aeP^4C}7S`gl*G)It>r6&B8`Hdq6^+t8u@<+RCX;o;)YN19 zn(fO^epUGO$j(h?PD-I4rP|J)8oK_E$-U2=8R>lRTk(s}j=nbfi$^b?SGs=k7xl^o z_160zK7H-ELbzr9H(vjK@wT$-yT7|wj=q@r*`I$p{+qkUes$-votEJKalMl@I|7Vl zrNds6lh_*DO_S~0D{Rl#yZ>f!*L{*jJ6a%5hOJ>xti+9B@O;P0K4XrtPikttSF5YF zmefi+NKvN7*2jKB=0NN~$9z zQj{sGq+(JlMUm7?s-a9FWs`bI#gqx842m+ToHB-#Mv9`$A%#<>P*#yDNU@Yzq^xOG zlL|>KER>O^ea7{Gexu#FWMW_Wzm<`^moSuJ*|4vvu`}xoKi?92XZOm!&NKcc1KU#T z&gC||S!MopBHDaE-8WXvioseb$;H8GMOb-S;ZsZE)C%^_k{J7peuCY!`WxVz z^LTf*dc64Kmg9*EKNSHgc2E&)L*eBH7A?%)ooxe*ne(70x|lg1=VUkmm3F0hHd2ufnFdz0Zqz9j2+V?m$kVj8(Q0;mueaH@wCnoYllxq& zZ{s`~XgRmk{mt7W?H5kpdgsHb(3any9Kf;EJP`ds=B3P~b&ZyINsk%67TVecItZwZ{1| zt6lU2nliDIF}6WFfc8ms@Kuz{m7*$3^{9(u`GO*sWobF4Rm!QuRy34Ga)p8{ zSx_w~Sy7g-adEbP&Uuc7SdCux`k8sqfBShOL2Iqyv*yU^t^C>Mc2jNi>{gqp{_r{F zukELu-DkH(821aCXJ6VXs(7xS~&%Q}t(7`vq;j>!2e zi{r<R1`M4{mO7i5OIHpwcYRR>$>G08(CYSy=sI|!ptlHyo zmzvGX=U#Q-1&dcR2QZtK5)rz7#)zUQU z#Q133?`clMXXtMBoVefP{I&&7D{4+0ljQr~a!eSN#WLAH?ltvPz2E1o{nPu?!dsNH z<*_1m`nXHU3B_!&N}()Nv&G`r1o2I{N>*>6&C|xY5D%B(eNH3S#79T^Bu#QEfkv9K0(I)GwtDt4*eOW zHjCCC;rR(3<8ap|!Cf1KG7Ul#>15#XbgBnN5#vKUB6+2f=}!1lTzp&zC(44_lRJ=BsiDQw`vEiiGQ;Kv%M+I)uD&4~X4| zuqHm2O$I#i(Y{=)gU<=cgeRTsACbEE(|RjXacjER%{3Ws~}tOkPoh z?*5@U>-36TqMG8qz%}KARqQu>(%KJc@3P3c#zpM6s00zKe&r+gOHEX=$@qB2m`6|( zWihM_NnvHeh{Jdq-`ceAlk8dC1D{^8`VsL{=d~MpiqG}w74izSVXS+RI0{_!iA*wX z#M!Gz-CgvaBcu%#D(f1Xkds|iF(35l`;$>b zHC_-nzowO*fkTy^4u3VGYFeFLXFgFCIb~d+IXfcN*l48Eb1)jzlc-~i@+CtBWv7uk zaxe0g(yQ-~mTBEu({$=0izwB?#=fARm-@GNqS#4ObRlVZp0ag16}2k43eAhN-rh&~ z%k%M>@g3TD%?aiI^d+Jan$a)cGhq1RL)ywP2@VtFe;QfOBjGjY(~A!ABicMPiSAE( z)K`go!P78p0mh5Q(y0Sx#KqW~3fbZl6}j9h77DG}!PsORd`{O;?; HKU4k(hL7`& literal 0 HcmV?d00001 diff --git a/examples/jaffleshop/data/raw_payments.parquet b/examples/jaffleshop/data/raw_payments.parquet new file mode 100644 index 0000000000000000000000000000000000000000..26242d16948c62bc9a5b06a7e11601f11251ee1a GIT binary patch literal 4548 zcmeI0e{36P8OP6#U#mm+-i=8-4O4xSJ567&tBP8k}dOc9i=NE2j$pejY{U>$*#E{JJRwsjpX&_9@<;>W78KL!(0*w_#VB);$2 z`BkTrCeevMIOTKi-SfQf`@HY-JnwT>23Vh!(J;p;QLwdSC%}f(CFGxEt&QjbImO0{4JsUm}00w~%><2^O0PuqV2!ap@g9pGccn~}U zM!+cG01qNS08tPFV;~MhFb*bw1QI|7NxiN!Mdh!9g_fb1H&x7^RgEOVRg9L=)*5xo zN~2Cy=w_fAHjqM@UOHHU=9)@XR`{r^JJck+d7j1<}plXqFU zK6#6!m}7(F)pa|*H`=`Qv9dd=zvyXTE|z}goY1<>`|7)@&vx(dy_Ru&YiOtc;(LeB z@wO{R{UnEnz0d#gy-(gZI?zyC{rU4dY)=o{p6q|>UHS{|r+)SA*Jode{ouE|o`3t_ zhhHkJ-m<`~Ca*4;7NY5qiKv_zNklX8WZ^I`stqryEsq*4|1{Kopw$icezk--q4qGH%I=p`y0=QGQkCX% zSt-6+0$rw~UZ!bUb5cv|G(j~@s(O{?Upl&Ki%Qc_PM1?h^*YVpRCJYQrWD#neHAsY zZ>KKMx8eg-Letdm^bMK?4fKnfpqKRX_>P%S%Z=0#^wZ(lgZ7v{PA%!@I$t1F1=lEX zE-twBl!|$Fea&4YA;M+U1tmrv`TqZ9r5{)=Z_btmXI-al@9^OErUZ~nFZ z4|K_PV$*W1ZZ$ErTq2p3Gx^GTN^4lsSk7#>yt5T)S-hqjtaPK$)C}7xqjoY9k0v+< zpMFifnpE)o!^&x(mh5*>($sie%KLI9cE(jla+wGK;bhof(NFrEEf;H19Dz@L;3KoPQlrbkXai%IhJs z%q61eZWaYI2E@duB#f+=<&BJFa;bbrIn&lk`pVfvDpxEBxgX`;FvDsq%b;h%@7)c7z;{7K1@n|GNz~3AS_~L@Ihpaa%1ZE~I28`e zOayKAI3LK*$+x&C!a;Y83*_gGID6rb-q{=4mHnAWpA-}Nr0M0o^75d~o8NCH9Q4I8 zzBxqv;KUxBGaa5T;^$SHuwlpgvqEnN>7V3ndE4QHB=JsPY>gkMLqRDM#=e6#t0Xw% zQw6^k;^_rY+$TktQLuNS%Ci<7wUvVW5ALtOjT#Swn$uws9T z&o@}#I{nS?T_F#1;XrRQ-{K;%hA(GQA=w>|$P-FlARc&jPYF3C=WK}QEEnj=D)~0v zWA%))*?!TDII!aC8pBdV_NI7St1?Hw*qZZ<_lPl^C7d6Y^^o)9P!82T@~#{*N0J;} zqG02SkQ}Off8~{Rk_NdxB_L+dL<;>`_5jgzg_g%VeB_;-AUDMxd0<(^)CTzh)gbAI z^Ut!g1DJ)ZY|y2g{5~RFDJ8H%UY)DCPhQ#nAq;9KPYz`eCPT47eF%|Vq97=fD=N~b zu)heAuUVfmHO>m8W2As#d?as=bR;w4qKu{#mp+({W_CqVsa;CpG!+V?DPJ;c$WNn{ OKPrx2ngjU%&3^!mo(E_E literal 0 HcmV?d00001 From bea1714a86a63c504b1f2b0ccd158b2e89ff40d0 Mon Sep 17 00:00:00 2001 From: zilto Date: Tue, 2 Apr 2024 14:32:18 -0400 Subject: [PATCH 3/3] refactored to --- examples/ibis/{ => feature_engineering}/README.md | 0 .../{ => feature_engineering}/column_dataflow.py | 0 examples/ibis/{ => feature_engineering}/columns.png | Bin .../{ => feature_engineering}/ibis_feature_set.png | Bin .../ibis/{ => feature_engineering}/requirements.txt | 0 examples/ibis/{ => feature_engineering}/run.py | 0 .../{ => feature_engineering}/table_dataflow.py | 0 examples/ibis/{ => feature_engineering}/tables.png | Bin examples/{jaffleshop => ibis/jaffle_shop}/LICENSE | 0 examples/{jaffleshop => ibis/jaffle_shop}/README.md | 2 +- .../jaffle_shop}/all_functions.png | Bin .../jaffle_shop}/data/raw_customers.parquet | Bin .../jaffle_shop}/data/raw_orders.parquet | Bin .../jaffle_shop}/data/raw_payments.parquet | Bin .../jaffle_shop}/dataflows/__init__.py | 0 .../jaffle_shop}/dataflows/customer_flow.py | 0 .../jaffle_shop}/dataflows/order_flow.py | 0 .../jaffle_shop}/dataflows/staging.py | 0 .../jaffle_shop}/requirements.txt | 0 examples/{jaffleshop => ibis/jaffle_shop}/run.py | 0 20 files changed, 1 insertion(+), 1 deletion(-) rename examples/ibis/{ => feature_engineering}/README.md (100%) rename examples/ibis/{ => feature_engineering}/column_dataflow.py (100%) rename examples/ibis/{ => feature_engineering}/columns.png (100%) rename examples/ibis/{ => feature_engineering}/ibis_feature_set.png (100%) rename examples/ibis/{ => feature_engineering}/requirements.txt (100%) rename examples/ibis/{ => feature_engineering}/run.py (100%) rename examples/ibis/{ => feature_engineering}/table_dataflow.py (100%) rename examples/ibis/{ => feature_engineering}/tables.png (100%) rename examples/{jaffleshop => ibis/jaffle_shop}/LICENSE (100%) rename examples/{jaffleshop => ibis/jaffle_shop}/README.md (90%) rename examples/{jaffleshop => ibis/jaffle_shop}/all_functions.png (100%) rename examples/{jaffleshop => ibis/jaffle_shop}/data/raw_customers.parquet (100%) rename examples/{jaffleshop => ibis/jaffle_shop}/data/raw_orders.parquet (100%) rename examples/{jaffleshop => ibis/jaffle_shop}/data/raw_payments.parquet (100%) rename examples/{jaffleshop => ibis/jaffle_shop}/dataflows/__init__.py (100%) rename examples/{jaffleshop => ibis/jaffle_shop}/dataflows/customer_flow.py (100%) rename examples/{jaffleshop => ibis/jaffle_shop}/dataflows/order_flow.py (100%) rename examples/{jaffleshop => ibis/jaffle_shop}/dataflows/staging.py (100%) rename examples/{jaffleshop => ibis/jaffle_shop}/requirements.txt (100%) rename examples/{jaffleshop => ibis/jaffle_shop}/run.py (100%) diff --git a/examples/ibis/README.md b/examples/ibis/feature_engineering/README.md similarity index 100% rename from examples/ibis/README.md rename to examples/ibis/feature_engineering/README.md diff --git a/examples/ibis/column_dataflow.py b/examples/ibis/feature_engineering/column_dataflow.py similarity index 100% rename from examples/ibis/column_dataflow.py rename to examples/ibis/feature_engineering/column_dataflow.py diff --git a/examples/ibis/columns.png b/examples/ibis/feature_engineering/columns.png similarity index 100% rename from examples/ibis/columns.png rename to examples/ibis/feature_engineering/columns.png diff --git a/examples/ibis/ibis_feature_set.png b/examples/ibis/feature_engineering/ibis_feature_set.png similarity index 100% rename from examples/ibis/ibis_feature_set.png rename to examples/ibis/feature_engineering/ibis_feature_set.png diff --git a/examples/ibis/requirements.txt b/examples/ibis/feature_engineering/requirements.txt similarity index 100% rename from examples/ibis/requirements.txt rename to examples/ibis/feature_engineering/requirements.txt diff --git a/examples/ibis/run.py b/examples/ibis/feature_engineering/run.py similarity index 100% rename from examples/ibis/run.py rename to examples/ibis/feature_engineering/run.py diff --git a/examples/ibis/table_dataflow.py b/examples/ibis/feature_engineering/table_dataflow.py similarity index 100% rename from examples/ibis/table_dataflow.py rename to examples/ibis/feature_engineering/table_dataflow.py diff --git a/examples/ibis/tables.png b/examples/ibis/feature_engineering/tables.png similarity index 100% rename from examples/ibis/tables.png rename to examples/ibis/feature_engineering/tables.png diff --git a/examples/jaffleshop/LICENSE b/examples/ibis/jaffle_shop/LICENSE similarity index 100% rename from examples/jaffleshop/LICENSE rename to examples/ibis/jaffle_shop/LICENSE diff --git a/examples/jaffleshop/README.md b/examples/ibis/jaffle_shop/README.md similarity index 90% rename from examples/jaffleshop/README.md rename to examples/ibis/jaffle_shop/README.md index 34deccc7a..2bd92f109 100644 --- a/examples/jaffleshop/README.md +++ b/examples/ibis/jaffle_shop/README.md @@ -12,7 +12,7 @@ The content and structure aims to match the [original dbt `jaffle_shop`](https:/ - `dataflows/customer_flow.py` and `dataflows/order_flow.py` define data transformations; equivalent to `models/customers.sql` and `models/orders.sql` in the dbt repo. - `run.py` specify where to load data from and how to execute dataflows. - Genrally, you'll notice Hamilton aims to reduce the sprawl of configurations (`.yaml`) and documentation (`.md`). Instead, it uses docstrings, type hints, or Python object to couple them to with your code (`.py`). + Generally, you'll notice Hamilton aims to reduce the sprawl of configurations (`.yaml`) and documentation (`.md`). Instead, it uses docstrings, type hints, or Python object to couple them to with your code (`.py`). ## Set up 1. create and activate virtual environment diff --git a/examples/jaffleshop/all_functions.png b/examples/ibis/jaffle_shop/all_functions.png similarity index 100% rename from examples/jaffleshop/all_functions.png rename to examples/ibis/jaffle_shop/all_functions.png diff --git a/examples/jaffleshop/data/raw_customers.parquet b/examples/ibis/jaffle_shop/data/raw_customers.parquet similarity index 100% rename from examples/jaffleshop/data/raw_customers.parquet rename to examples/ibis/jaffle_shop/data/raw_customers.parquet diff --git a/examples/jaffleshop/data/raw_orders.parquet b/examples/ibis/jaffle_shop/data/raw_orders.parquet similarity index 100% rename from examples/jaffleshop/data/raw_orders.parquet rename to examples/ibis/jaffle_shop/data/raw_orders.parquet diff --git a/examples/jaffleshop/data/raw_payments.parquet b/examples/ibis/jaffle_shop/data/raw_payments.parquet similarity index 100% rename from examples/jaffleshop/data/raw_payments.parquet rename to examples/ibis/jaffle_shop/data/raw_payments.parquet diff --git a/examples/jaffleshop/dataflows/__init__.py b/examples/ibis/jaffle_shop/dataflows/__init__.py similarity index 100% rename from examples/jaffleshop/dataflows/__init__.py rename to examples/ibis/jaffle_shop/dataflows/__init__.py diff --git a/examples/jaffleshop/dataflows/customer_flow.py b/examples/ibis/jaffle_shop/dataflows/customer_flow.py similarity index 100% rename from examples/jaffleshop/dataflows/customer_flow.py rename to examples/ibis/jaffle_shop/dataflows/customer_flow.py diff --git a/examples/jaffleshop/dataflows/order_flow.py b/examples/ibis/jaffle_shop/dataflows/order_flow.py similarity index 100% rename from examples/jaffleshop/dataflows/order_flow.py rename to examples/ibis/jaffle_shop/dataflows/order_flow.py diff --git a/examples/jaffleshop/dataflows/staging.py b/examples/ibis/jaffle_shop/dataflows/staging.py similarity index 100% rename from examples/jaffleshop/dataflows/staging.py rename to examples/ibis/jaffle_shop/dataflows/staging.py diff --git a/examples/jaffleshop/requirements.txt b/examples/ibis/jaffle_shop/requirements.txt similarity index 100% rename from examples/jaffleshop/requirements.txt rename to examples/ibis/jaffle_shop/requirements.txt diff --git a/examples/jaffleshop/run.py b/examples/ibis/jaffle_shop/run.py similarity index 100% rename from examples/jaffleshop/run.py rename to examples/ibis/jaffle_shop/run.py