From 452713ccc2ce67def35153f741ffdc500b7c0795 Mon Sep 17 00:00:00 2001 From: colton Date: Tue, 17 Dec 2024 16:57:48 -0500 Subject: [PATCH] [devrel] atproto demo ingestion (#26038) ## Summary & Motivation Scaffolds project for atproto dashboard demo. ``` dagster project scaffold --name project_atproto_dashboard ``` Adds ingestion for Bluesky; snapshots starter pack and user feeds. See upstream PRs that were squashed for context. ## How I Tested These Changes ## Changelog > Insert changelog entry or delete this section. --------- Co-authored-by: Alex Noonan --- .../project_atproto_dashboard/.env.example | 17 +++ examples/project_atproto_dashboard/.gitignore | 5 + examples/project_atproto_dashboard/README.md | 52 +++++++ .../architecture-diagram.png | Bin 0 -> 81974 bytes .../project_atproto_dashboard/dagster.yaml | 6 + .../dbt_project/.gitignore | 0 .../dbt_project/.sqlfluff | 2 + .../dbt_project/dbt_project.yml | 13 ++ .../models/analysis/activity_over_time.sql | 14 ++ .../models/analysis/all_profiles.sql | 105 +++++++++++++ .../dbt_project/models/analysis/calendar.sql | 45 ++++++ .../models/analysis/latest_feed.sql | 57 ++++++++ .../dbt_project/models/analysis/schema.yml | 18 +++ .../models/analysis/top_daily_posts.sql | 46 ++++++ .../models/analysis/top_external_links.sql | 73 +++++++++ .../dbt_project/models/sources.yml | 14 ++ .../dbt_project/models/staging/schema.yml | 7 + .../models/staging/stg_feed_snapshots.sql | 5 + .../models/staging/stg_profiles.sql | 5 + .../dbt_project/profiles.yml | 27 ++++ .../project_atproto_dashboard/lineage.svg | 3 + .../project_atproto_dashboard/__init__.py | 1 + .../dashboard/__init__.py | 0 .../dashboard/definitions.py | 49 +++++++ .../project_atproto_dashboard/definitions.py | 9 ++ .../ingestion/__init__.py | 0 .../ingestion/definitions.py | 138 ++++++++++++++++++ .../ingestion/resources.py | 29 ++++ .../ingestion/utils/__init__.py | 0 .../ingestion/utils/atproto.py | 59 ++++++++ .../modeling/__init__.py | 0 .../modeling/definitions.py | 45 ++++++ .../__init__.py | 1 + .../test_assets.py | 1 + .../project_atproto_dashboard/pyproject.toml | 34 +++++ pyright/alt-1/requirements-pinned.txt | 40 ++--- pyright/master/requirements-pinned.txt | 86 +++++------ pyright/master/requirements.txt | 1 + 38 files changed, 946 insertions(+), 61 deletions(-) create mode 100644 examples/project_atproto_dashboard/.env.example create mode 100644 examples/project_atproto_dashboard/.gitignore create mode 100644 examples/project_atproto_dashboard/README.md create mode 100644 examples/project_atproto_dashboard/architecture-diagram.png create mode 100644 examples/project_atproto_dashboard/dagster.yaml create mode 100644 examples/project_atproto_dashboard/dbt_project/.gitignore create mode 100644 examples/project_atproto_dashboard/dbt_project/.sqlfluff create mode 100644 examples/project_atproto_dashboard/dbt_project/dbt_project.yml create mode 100644 examples/project_atproto_dashboard/dbt_project/models/analysis/activity_over_time.sql create mode 100644 examples/project_atproto_dashboard/dbt_project/models/analysis/all_profiles.sql create mode 100644 examples/project_atproto_dashboard/dbt_project/models/analysis/calendar.sql create mode 100644 examples/project_atproto_dashboard/dbt_project/models/analysis/latest_feed.sql create mode 100644 examples/project_atproto_dashboard/dbt_project/models/analysis/schema.yml create mode 100644 examples/project_atproto_dashboard/dbt_project/models/analysis/top_daily_posts.sql create mode 100644 examples/project_atproto_dashboard/dbt_project/models/analysis/top_external_links.sql create mode 100644 examples/project_atproto_dashboard/dbt_project/models/sources.yml create mode 100644 examples/project_atproto_dashboard/dbt_project/models/staging/schema.yml create mode 100644 examples/project_atproto_dashboard/dbt_project/models/staging/stg_feed_snapshots.sql create mode 100644 examples/project_atproto_dashboard/dbt_project/models/staging/stg_profiles.sql create mode 100644 examples/project_atproto_dashboard/dbt_project/profiles.yml create mode 100644 examples/project_atproto_dashboard/lineage.svg create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard/__init__.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard/dashboard/__init__.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard/dashboard/definitions.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard/definitions.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/__init__.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/definitions.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/resources.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/utils/__init__.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/utils/atproto.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard/modeling/__init__.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard/modeling/definitions.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard_tests/__init__.py create mode 100644 examples/project_atproto_dashboard/project_atproto_dashboard_tests/test_assets.py create mode 100644 examples/project_atproto_dashboard/pyproject.toml diff --git a/examples/project_atproto_dashboard/.env.example b/examples/project_atproto_dashboard/.env.example new file mode 100644 index 0000000000000..4ea1e239f6bb6 --- /dev/null +++ b/examples/project_atproto_dashboard/.env.example @@ -0,0 +1,17 @@ +AWS_ENDPOINT_URL= +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= +AWS_BUCKET_NAME= +AWS_ACCOUNT_ID= + +MOTHERDUCK_TOKEN= + +BSKY_LOGIN= +BSKY_APP_PASSWORD= + +DBT_TARGET= + +AZURE_POWERBI_CLIENT_ID= +AZURE_POWERBI_CLIENT_SECRET= +AZURE_POWERBI_TENANT_ID= +AZURE_POWERBI_WORKSPACE_ID= diff --git a/examples/project_atproto_dashboard/.gitignore b/examples/project_atproto_dashboard/.gitignore new file mode 100644 index 0000000000000..ace8bc76e6a41 --- /dev/null +++ b/examples/project_atproto_dashboard/.gitignore @@ -0,0 +1,5 @@ +tmp*/ +storage/ +schedules/ +history/ +atproto-session.txt diff --git a/examples/project_atproto_dashboard/README.md b/examples/project_atproto_dashboard/README.md new file mode 100644 index 0000000000000..4c15d93cc89cf --- /dev/null +++ b/examples/project_atproto_dashboard/README.md @@ -0,0 +1,52 @@ +# project_atproto_dashboard + +An end-to-end demonstration of ingestion data from the ATProto API, modeling it with dbt, and presenting it with Power BI. + +![Architecture Diagram](./architecture-diagram.png) + +![Project asset lineage](./lineage.svg) + +## Features used + +1. Ingestion of data-related Bluesky posts + - Dynamic partitions + - Declarative automation + - Concurrency limits +2. Modelling data using _dbt_ +3. Representing data in a dashboard + +## Getting started + +### Environment Setup + +Ensure the following environments have been populated in your `.env` file. Start by copying the +template. + +``` +cp .env.example .env +``` + +And then populate the fields. + +### Development + +Install the project dependencies: + + pip install -e ".[dev]" + +Start Dagster: + + DAGSTER_HOME=$(pwd) dagster dev + +### Unit testing + +Tests are in the `project_atproto_dashboard_tests` directory and you can run tests using `pytest`: + + pytest project_atproto_dashboard_tests + +## Resources + +- https://docs.bsky.app/docs/tutorials/viewing-feeds +- https://docs.bsky.app/docs/advanced-guides/rate-limits +- https://atproto.blue/en/latest/atproto_client/auth.html#session-string +- https://tenacity.readthedocs.io/en/latest/#waiting-before-retrying diff --git a/examples/project_atproto_dashboard/architecture-diagram.png b/examples/project_atproto_dashboard/architecture-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..af16cc6c57430fa742d215eae14e8dbc45d778dc GIT binary patch literal 81974 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 literal 0 HcmV?d00001 diff --git a/examples/project_atproto_dashboard/dagster.yaml b/examples/project_atproto_dashboard/dagster.yaml new file mode 100644 index 0000000000000..c9705420e83ca --- /dev/null +++ b/examples/project_atproto_dashboard/dagster.yaml @@ -0,0 +1,6 @@ +run_coordinator: + module: dagster.core.run_coordinator + class: QueuedRunCoordinator + +concurrency: + default_op_concurrency_limit: 1 diff --git a/examples/project_atproto_dashboard/dbt_project/.gitignore b/examples/project_atproto_dashboard/dbt_project/.gitignore new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/examples/project_atproto_dashboard/dbt_project/.sqlfluff b/examples/project_atproto_dashboard/dbt_project/.sqlfluff new file mode 100644 index 0000000000000..6fffb098b0115 --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/.sqlfluff @@ -0,0 +1,2 @@ +[sqlfluff:rules:capitalisation.keywords] +capitalisation_policy = upper diff --git a/examples/project_atproto_dashboard/dbt_project/dbt_project.yml b/examples/project_atproto_dashboard/dbt_project/dbt_project.yml new file mode 100644 index 0000000000000..5dc13e8c3997a --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/dbt_project.yml @@ -0,0 +1,13 @@ +name: "dbt_project" +version: "1.0.0" +config-version: 2 + +profile: "bluesky" + +target-path: "target" +clean-targets: + - "target" + - "dbt_packages" + +models: + +materialized: table diff --git a/examples/project_atproto_dashboard/dbt_project/models/analysis/activity_over_time.sql b/examples/project_atproto_dashboard/dbt_project/models/analysis/activity_over_time.sql new file mode 100644 index 0000000000000..794065c8723e7 --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/models/analysis/activity_over_time.sql @@ -0,0 +1,14 @@ +WITH final AS ( + SELECT + date_trunc('day', created_at) AS post_date, + count(DISTINCT post_text) AS unique_posts, + count(DISTINCT author_handle) AS active_authors, + sum(likes) AS total_likes, + sum(replies) AS total_comments, + sum(quotes) AS total_quotes + FROM {{ ref("latest_feed") }} + GROUP BY date_trunc('day', created_at) + ORDER BY date_trunc('day', created_at) DESC +) + +SELECT * FROM final diff --git a/examples/project_atproto_dashboard/dbt_project/models/analysis/all_profiles.sql b/examples/project_atproto_dashboard/dbt_project/models/analysis/all_profiles.sql new file mode 100644 index 0000000000000..5f4e21734bff1 --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/models/analysis/all_profiles.sql @@ -0,0 +1,105 @@ +WITH max_profile_data AS ( + SELECT + json_extract_string(json, '$.subject.did') AS profile_did, + max( + strptime( + regexp_extract( + filename, + 'dagster-demo/atproto_starter_pack_snapshot/(\d{4}-\d{2}-\d{2}/\d{2}/\d{2})', + 1 + ), + '%Y-%m-%d/%H/%M' + ) + ) AS max_extracted_timestamp + FROM {{ ref("stg_profiles") }} + GROUP BY + json_extract_string(json, '$.subject.did') +), + +profiles AS ( + SELECT + json_extract_string(json, '$.subject.handle') AS handle_subject, + json_extract_string(json, '$.subject.did') AS profile_did, + json_extract_string(json, '$.subject.avatar') AS profile_avatar, + json_extract_string(json, '$.subject.display_name') + AS profile_display_name, + json_extract_string(json, '$.subject.created_at') + AS profile_created_date, + json_extract_string(json, '$.subject.description') + AS profile_description + FROM {{ ref("stg_profiles") }} stg_prof + JOIN max_profile_data + ON + json_extract_string(stg_prof.json, '$.subject.did') + = max_profile_data.profile_did + AND strptime( + regexp_extract( + stg_prof.filename, + 'dagster-demo/atproto_starter_pack_snapshot/(\d{4}-\d{2}-\d{2}/\d{2}/\d{2})', + 1 + ), + '%Y-%m-%d/%H/%M' + ) + = max_profile_data.max_extracted_timestamp +), + +user_aggregates AS ( + SELECT + replace(author_handle, '"', '') AS author_handle, + count(*) AS num_posts, + avg(cast(lf.likes AS int)) AS average_likes, + sum(cast(lf.likes AS int)) AS total_likes, + sum(cast(lf.replies AS int)) AS total_replies, + sum(cast(lf.likes AS int)) / count(*) AS total_likes_by_num_of_posts, + round( + count(*) + / count(DISTINCT date_trunc('day', cast(created_at AS timestamp))), + 2 + ) AS avg_posts_per_day, + ntile(100) + OVER ( + ORDER BY sum(cast(lf.likes AS int)) + ) + AS likes_percentile, + ntile(100) + OVER ( + ORDER BY sum(cast(lf.replies AS int)) + ) + AS replies_percentile, + ntile(100) OVER ( + ORDER BY count(*) + ) AS posts_percentile, + (ntile(100) OVER ( + ORDER BY sum(cast(lf.likes AS int))) + ntile(100) OVER ( + ORDER BY sum(cast(lf.replies AS int))) + ntile(100) OVER ( + ORDER BY count(*) + )) + / 3.0 AS avg_score + FROM {{ ref("latest_feed") }} lf + GROUP BY replace(author_handle, '"', '') +), + +final AS ( + SELECT DISTINCT + profiles.handle_subject AS profile_handle, + profiles.profile_did, + profiles.profile_display_name, + profiles.profile_avatar, + profiles.profile_created_date, + profiles.profile_description, + user_aggregates.num_posts, + user_aggregates.average_likes, + user_aggregates.total_likes, + user_aggregates.total_replies, + user_aggregates.total_likes_by_num_of_posts, + user_aggregates.avg_posts_per_day, + user_aggregates.likes_percentile, + user_aggregates.replies_percentile, + user_aggregates.posts_percentile, + user_aggregates.avg_score + FROM profiles + LEFT JOIN user_aggregates + ON user_aggregates.author_handle = profiles.handle_subject +) + +SELECT * FROM final diff --git a/examples/project_atproto_dashboard/dbt_project/models/analysis/calendar.sql b/examples/project_atproto_dashboard/dbt_project/models/analysis/calendar.sql new file mode 100644 index 0000000000000..91f1ae0ea62e0 --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/models/analysis/calendar.sql @@ -0,0 +1,45 @@ +WITH date_spine AS ( + SELECT CAST(range AS DATE) AS date_key + FROM RANGE( + (SELECT MIN(created_at) FROM {{ ref("latest_feed") }}), + CURRENT_DATE(), + INTERVAL 1 DAY + ) +) + +SELECT + date_key AS date_key, + DAYOFYEAR(date_key) AS day_of_year, + WEEKOFYEAR(date_key) AS week_of_year, + DAYOFWEEK(date_key) AS day_of_week, + ISODOW(date_key) AS iso_day_of_week, + DAYNAME(date_key) AS day_name, + DATE_TRUNC('week', date_key) AS first_day_of_week, + DATE_TRUNC('week', date_key) + 6 AS last_day_of_week, + YEAR(date_key) || RIGHT('0' || MONTH(date_key), 2) AS month_key, + MONTH(date_key) AS month_of_year, + DAYOFMONTH(date_key) AS day_of_month, + LEFT(MONTHNAME(date_key), 3) AS month_name_short, + MONTHNAME(date_key) AS month_name, + DATE_TRUNC('month', date_key) AS first_day_of_month, + LAST_DAY(date_key) AS last_day_of_month, + CAST(YEAR(date_key) || QUARTER(date_key) AS INT) AS quarter_key, + QUARTER(date_key) AS quarter_of_year, + CAST(date_key - DATE_TRUNC('Quarter', date_key) + 1 AS INT) + AS day_of_quarter, + ('Q' || QUARTER(date_key)) AS quarter_desc_short, + ('Quarter ' || QUARTER(date_key)) AS quarter_desc, + DATE_TRUNC('quarter', date_key) AS first_day_of_quarter, + LAST_DAY(DATE_TRUNC('quarter', date_key) + INTERVAL 2 MONTH) + AS last_day_of_quarter, + CAST(YEAR(date_key) AS INT) AS year_key, + DATE_TRUNC('Year', date_key) AS first_day_of_year, + DATE_TRUNC('Year', date_key) - 1 + INTERVAL 1 YEAR AS last_day_of_year, + ROW_NUMBER() + OVER ( + PARTITION BY YEAR(date_key), MONTH(date_key), DAYOFWEEK(date_key) + ORDER BY date_key + ) + AS ordinal_weekday_of_month +FROM date_spine +WHERE CAST(YEAR(date_key) AS INT) >= 2020 diff --git a/examples/project_atproto_dashboard/dbt_project/models/analysis/latest_feed.sql b/examples/project_atproto_dashboard/dbt_project/models/analysis/latest_feed.sql new file mode 100644 index 0000000000000..09f0e23ab1855 --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/models/analysis/latest_feed.sql @@ -0,0 +1,57 @@ +WITH max_update AS ( + SELECT + max( + strptime( + regexp_extract( + filename, + 'dagster-demo/atproto_actor_feed_snapshot/(\d{4}-\d{2}-\d{2}/\d{2}/\d{2})', + 1 + ), + '%Y-%m-%d/%H/%M' + ) + ) AS max_extracted_timestamp, + regexp_extract(filename, 'did:(.*?)\.json') AS profile_id + FROM {{ ref("stg_feed_snapshots") }} + GROUP BY + regexp_extract(filename, 'did:(.*?)\.json') +), + +final AS ( + SELECT + json_extract_string(sfs.json, '$.post.author.handle') AS author_handle, + json_extract_string(sfs.json, '$.post.author.did') AS author_id, + cast(sfs.json.post.like_count AS int) AS likes, + cast(sfs.json.post.quote_count AS int) AS quotes, + cast(sfs.json.post.reply_count AS int) AS replies, + json_extract_string(sfs.json, '$.post.record.text') AS post_text, + sfs.json.post.record.embed, + json_extract_string( + sfs.json, '$.post.record.embed.external.description' + ) AS external_embed_description, + json_extract_string(sfs.json, '$.post.record.embed.external.uri') + AS external_embed_link, + sfs.json.post.record.embed.external.thumb AS external_embed_thumbnail, + cast(sfs.json.post.record.created_at AS timestamp) AS created_at, + CASE + WHEN json_extract_string(sfs.json.post.record.embed, '$.images[0].image.ref.link') IS NULL THEN NULL + ELSE concat('https://cdn.bsky.app/img/feed_thumbnail/plain/', json_extract_string(sfs.json, '$.post.author.did') ,'/' ,json_extract_string(sfs.json.post.record.embed, '$.images[0].image.ref.link'), '@jpeg') + END AS image_url, + max_update.max_extracted_timestamp, + max_update.profile_id + FROM {{ ref("stg_feed_snapshots") }} sfs + JOIN max_update + ON + max_update.profile_id + = regexp_extract(sfs.filename, 'did:(.*?)\.json') + AND max_update.max_extracted_timestamp + = strptime( + regexp_extract( + sfs.filename, + 'dagster-demo/atproto_actor_feed_snapshot/(\d{4}-\d{2}-\d{2}/\d{2}/\d{2})', + 1 + ), + '%Y-%m-%d/%H/%M' + ) +) + +SELECT * FROM final diff --git a/examples/project_atproto_dashboard/dbt_project/models/analysis/schema.yml b/examples/project_atproto_dashboard/dbt_project/models/analysis/schema.yml new file mode 100644 index 0000000000000..404d89541a5db --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/models/analysis/schema.yml @@ -0,0 +1,18 @@ +version: 2 + +models: + - name: all_profiles + description: "table showing data for all the profiles posts are collected from and some high level statistics" + columns: + - name: profile_handle + data_tests: + - unique + - not_null + - name: latest_feed + description: "the latest feed of posts" + - name: activity_over_time + description: "daily activity of posts overtime" + - name: top_daily_posts + description: "top posts ranked for a given day" + - name: top_external_links + description: "top external content grouped by type shared in the community" diff --git a/examples/project_atproto_dashboard/dbt_project/models/analysis/top_daily_posts.sql b/examples/project_atproto_dashboard/dbt_project/models/analysis/top_daily_posts.sql new file mode 100644 index 0000000000000..a53c8435f60c6 --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/models/analysis/top_daily_posts.sql @@ -0,0 +1,46 @@ +WITH distinct_posts AS ( + SELECT DISTINCT ON (author_handle, post_text, date_trunc('day', created_at)) + author_handle, + post_text, + likes, + quotes, + replies, + image_url, + external_embed_link, + external_embed_thumbnail, + external_embed_description, + created_at + FROM {{ ref("latest_feed") }} +), + +scored_posts AS ( + SELECT + *, + (likes * 0.2) + (quotes * 0.4) + (replies * 0.4) AS engagement_score, + date_trunc('day', created_at) AS post_date, + row_number() OVER ( + PARTITION BY date_trunc('day', created_at) + ORDER BY (likes * 0.2) + (quotes * 0.4) + (replies * 0.4) DESC + ) AS daily_rank + FROM distinct_posts +), + +final AS ( + SELECT + post_date, + author_handle, + post_text, + likes, + quotes, + replies, + image_url, + external_embed_link, + external_embed_thumbnail, + external_embed_description, + round(engagement_score, 2) AS engagement_score, + daily_rank + FROM scored_posts + WHERE daily_rank <= 10 +) + +SELECT * FROM final diff --git a/examples/project_atproto_dashboard/dbt_project/models/analysis/top_external_links.sql b/examples/project_atproto_dashboard/dbt_project/models/analysis/top_external_links.sql new file mode 100644 index 0000000000000..5e207b0d664be --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/models/analysis/top_external_links.sql @@ -0,0 +1,73 @@ +WITH distinct_posts AS ( + SELECT DISTINCT ON (author_handle, post_text, date_trunc('day', created_at)) + author_handle, + post_text, + likes, + quotes, + replies, + created_at, + image_url, + embed, + external_embed_link, + external_embed_thumbnail, + external_embed_description, + CASE + WHEN external_embed_link LIKE '%youtu%' THEN 'YouTube' + WHEN external_embed_link LIKE '%docs%' THEN 'Docs' + WHEN external_embed_link LIKE '%github%' THEN 'GitHub' + WHEN external_embed_link LIKE '%substack%' THEN 'SubStack' + WHEN external_embed_link LIKE '%twitch%' THEN 'Twitch' + WHEN external_embed_link LIKE '%msnbc%' THEN 'News' + WHEN external_embed_link LIKE '%theguardian%' THEN 'News' + WHEN external_embed_link LIKE '%foreignpolicy%' THEN 'News' + WHEN external_embed_link LIKE '%nytimes%' THEN 'News' + WHEN external_embed_link LIKE '%wsj%' THEN 'News' + WHEN external_embed_link LIKE '%bloomberg%' THEN 'News' + WHEN external_embed_link LIKE '%theverge%' THEN 'News' + WHEN external_embed_link LIKE '%cnbc%' THEN 'News' + WHEN external_embed_link LIKE '%.ft.%' THEN 'News' + WHEN external_embed_link LIKE '%washingtonpost%' THEN 'News' + WHEN external_embed_link LIKE '%newrepublic%' THEN 'News' + WHEN external_embed_link LIKE '%huffpost%' THEN 'News' + WHEN external_embed_link LIKE '%wired%' THEN 'News' + WHEN external_embed_link LIKE '%medium%' THEN 'Medium' + WHEN external_embed_link LIKE '%reddit%' THEN 'Reddit' + WHEN external_embed_link LIKE '%/blog/%' THEN 'Blog' + ELSE 'Other' + END AS external_link_type + FROM {{ ref("latest_feed") }} + WHERE external_embed_link IS NOT null +), + +scored_posts AS ( + SELECT + *, + (likes * 0.2) + (quotes * 0.4) + (replies * 0.4) AS engagement_score, + date_trunc('day', created_at) AS post_date, + row_number() OVER ( + PARTITION BY date_trunc('day', created_at), external_link_type + ORDER BY (likes * 0.2) + (quotes * 0.4) + (replies * 0.4) DESC + ) AS daily_rank + FROM distinct_posts +), + +final AS ( + SELECT + post_date, + author_handle, + post_text, + likes, + quotes, + replies, + round(engagement_score, 2) AS engagement_score, + daily_rank, + embed, + external_embed_link, + external_embed_thumbnail, + external_embed_description, + external_link_type + FROM scored_posts + WHERE daily_rank <= 10 +) + +SELECT * FROM final diff --git a/examples/project_atproto_dashboard/dbt_project/models/sources.yml b/examples/project_atproto_dashboard/dbt_project/models/sources.yml new file mode 100644 index 0000000000000..8b9e72a31b2bc --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/models/sources.yml @@ -0,0 +1,14 @@ +version: 2 + +sources: + - name: r2_bucket + tables: + - name: actor_feed_snapshot + description: "external r2 bucket with json files of actor feeds" + meta: + external_location: "read_ndjson_objects('r2://dagster-demo/atproto_actor_feed_snapshot/**/*.json', filename=true)" + - name: starter_pack_snapshot + description: "external r2 bucket with json files for feed snapshots" + meta: + external_location: "read_ndjson_objects('r2://dagster-demo/atproto_starter_pack_snapshot/**/*.json', filename=true)" + diff --git a/examples/project_atproto_dashboard/dbt_project/models/staging/schema.yml b/examples/project_atproto_dashboard/dbt_project/models/staging/schema.yml new file mode 100644 index 0000000000000..61f4b3d774b18 --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/models/staging/schema.yml @@ -0,0 +1,7 @@ +version: 2 + +models: + - name: stg_profiles + description: "raw data from r2 bucket" + - name: stg_feed_snapshots + description: "raw posts data from r2 bucket" \ No newline at end of file diff --git a/examples/project_atproto_dashboard/dbt_project/models/staging/stg_feed_snapshots.sql b/examples/project_atproto_dashboard/dbt_project/models/staging/stg_feed_snapshots.sql new file mode 100644 index 0000000000000..92674ee054769 --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/models/staging/stg_feed_snapshots.sql @@ -0,0 +1,5 @@ +WITH raw AS ( + SELECT * FROM {{ source('r2_bucket', 'actor_feed_snapshot') }} +) + +SELECT * FROM raw diff --git a/examples/project_atproto_dashboard/dbt_project/models/staging/stg_profiles.sql b/examples/project_atproto_dashboard/dbt_project/models/staging/stg_profiles.sql new file mode 100644 index 0000000000000..7e4eeba113e2c --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/models/staging/stg_profiles.sql @@ -0,0 +1,5 @@ +WITH raw AS ( + SELECT * FROM {{ source('r2_bucket', 'starter_pack_snapshot') }} +) + +SELECT * FROM raw diff --git a/examples/project_atproto_dashboard/dbt_project/profiles.yml b/examples/project_atproto_dashboard/dbt_project/profiles.yml new file mode 100644 index 0000000000000..462a3a6a9e31b --- /dev/null +++ b/examples/project_atproto_dashboard/dbt_project/profiles.yml @@ -0,0 +1,27 @@ +bluesky: + target: prod + outputs: + dev: + type: duckdb + schema: bluesky_dev + path: "local.duckdb" + threads: 16 + extensions: + - httpfs + settings: + s3_region: "auto" + s3_access_key_id: "{{ env_var('AWS_ACCESS_KEY_ID') }}" + s3_secret_access_key: "{{ env_var('AWS_SECRET_ACCESS_KEY') }}" + s3_endpoint: "{{ env_var('AWS_ENDPOINT_URL') | replace('https://', '') }}" + prod: + type: duckdb + schema: bluesky + path: "md:prod_bluesky?MOTHERDUCK_TOKEN={{ env_var('MOTHERDUCK_TOKEN') }}" + threads: 16 + extensions: + - httpfs + settings: + s3_region: "auto" + s3_access_key_id: "{{ env_var('AWS_ACCESS_KEY_ID') }}" + s3_secret_access_key: "{{ env_var('AWS_SECRET_ACCESS_KEY') }}" + s3_endpoint: "{{ env_var('AWS_ENDPOINT_URL') | replace('https://', '') }}" diff --git a/examples/project_atproto_dashboard/lineage.svg b/examples/project_atproto_dashboard/lineage.svg new file mode 100644 index 0000000000000..578247eaff975 --- /dev/null +++ b/examples/project_atproto_dashboard/lineage.svg @@ -0,0 +1,3 @@ +
default
project_atproto_dashboard.definitions
staging
project_atproto_dashboard.definitions
analysis
project_atproto_dashboard.definitions
ingestion
project_atproto_dashboard.definitions
reporting
project_atproto_dashboard.definitions
Daily Rank
No description
Loading...
activity_over_time
daily activity of posts overtime
Never materialized
dbt
DuckDB
actor_feed_snapshot
Snapshot of full user feed written to S3 storage.
—
—
—
Loading...
Python
all_profiles
table showing data for all the profiles posts are collected from and some high level statistics
Never materialized
Checks
2
dbt
DuckDB
calendar
dbt model calendar
Never materialized
dbt
DuckDB
latest_feed
the latest feed of posts
Never materialized
dbt
DuckDB
powerbi_bluesky_report
No description
–
Power BI
Report
powerbi_bluesky_model
No description
–
Power BI
Semantic Model
starter_pack_snapshot
Snapshot of members in a Bluesky starter pack partitioned by starter pack ID and written to S3 storage.
—
—
—
Loading...
Python
stg_feed_snapshots
raw posts data from r2 bucket
Never materialized
dbt
DuckDB
stg_profiles
raw data from r2 bucket
Never materialized
dbt
DuckDB
top_daily_posts
top posts ranked for a given day
Never materialized
dbt
DuckDB
top_external_links
top external content grouped by type shared in the community
Never materialized
dbt
DuckDB
\ No newline at end of file diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard/__init__.py b/examples/project_atproto_dashboard/project_atproto_dashboard/__init__.py new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/examples/project_atproto_dashboard/project_atproto_dashboard/__init__.py @@ -0,0 +1 @@ + diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard/dashboard/__init__.py b/examples/project_atproto_dashboard/project_atproto_dashboard/dashboard/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard/dashboard/definitions.py b/examples/project_atproto_dashboard/project_atproto_dashboard/dashboard/definitions.py new file mode 100644 index 0000000000000..dba89ead146d3 --- /dev/null +++ b/examples/project_atproto_dashboard/project_atproto_dashboard/dashboard/definitions.py @@ -0,0 +1,49 @@ +import dagster as dg +from dagster_powerbi import ( + DagsterPowerBITranslator, + PowerBIServicePrincipal, + PowerBIWorkspace, + load_powerbi_asset_specs, +) +from dagster_powerbi.translator import PowerBIContentData + +power_bi_workspace = PowerBIWorkspace( + credentials=PowerBIServicePrincipal( + client_id=dg.EnvVar("AZURE_POWERBI_CLIENT_ID"), + client_secret=dg.EnvVar("AZURE_POWERBI_CLIENT_SECRET"), + tenant_id=dg.EnvVar("AZURE_POWERBI_TENANT_ID"), + ), + workspace_id=dg.EnvVar("AZURE_POWERBI_WORKSPACE_ID"), +) + + +class CustomDagsterPowerBITranslator(DagsterPowerBITranslator): + def get_report_spec(self, data: PowerBIContentData) -> dg.AssetSpec: + return ( + super() + .get_report_spec(data) + .replace_attributes( + group_name="reporting", + ) + ) + + def get_semantic_model_spec(self, data: PowerBIContentData) -> dg.AssetSpec: + upsteam_table_deps = [ + dg.AssetKey(table.get("name")) for table in data.properties.get("tables", []) + ] + return ( + super() + .get_semantic_model_spec(data) + .replace_attributes( + group_name="reporting", + deps=upsteam_table_deps, + ) + ) + + +power_bi_specs = load_powerbi_asset_specs( + power_bi_workspace, + dagster_powerbi_translator=CustomDagsterPowerBITranslator, +) + +defs = dg.Definitions(assets=[*power_bi_specs], resources={"power_bi": power_bi_workspace}) diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard/definitions.py b/examples/project_atproto_dashboard/project_atproto_dashboard/definitions.py new file mode 100644 index 0000000000000..f2a2b5ebd5f2e --- /dev/null +++ b/examples/project_atproto_dashboard/project_atproto_dashboard/definitions.py @@ -0,0 +1,9 @@ +import dagster as dg + +import project_atproto_dashboard.dashboard.definitions as dashboard_definitions +import project_atproto_dashboard.ingestion.definitions as ingestion_definitions +import project_atproto_dashboard.modeling.definitions as modeling_definitions + +defs = dg.Definitions.merge( + ingestion_definitions.defs, modeling_definitions.defs, dashboard_definitions.defs +) diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/__init__.py b/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/definitions.py b/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/definitions.py new file mode 100644 index 0000000000000..41b2f2e969c6b --- /dev/null +++ b/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/definitions.py @@ -0,0 +1,138 @@ +import os +from datetime import datetime + +import dagster as dg +from dagster_aws.s3 import S3Resource + +from project_atproto_dashboard.ingestion.resources import ATProtoResource +from project_atproto_dashboard.ingestion.utils.atproto import ( + get_all_feed_items, + get_all_starter_pack_members, +) + +AWS_BUCKET_NAME = os.environ.get("AWS_BUCKET_NAME", "dagster-demo") + + +atproto_did_dynamic_partition = dg.DynamicPartitionsDefinition(name="atproto_did_dynamic_partition") + + +@dg.asset( + partitions_def=dg.StaticPartitionsDefinition( + partition_keys=[ + "at://did:plc:lc5jzrr425fyah724df3z5ik/app.bsky.graph.starterpack/3l7cddlz5ja24", # https://bsky.app/starter-pack/christiannolan.bsky.social/3l7cddlz5ja24 + ] + ), + automation_condition=dg.AutomationCondition.on_cron("0 0 * * *"), # Midnight + kinds={"python"}, + group_name="ingestion", +) +def starter_pack_snapshot( + context: dg.AssetExecutionContext, + atproto_resource: ATProtoResource, + s3_resource: S3Resource, +) -> dg.MaterializeResult: + """Snapshot of members in a Bluesky starter pack partitioned by starter pack ID and written to S3 storage. + + Args: + context (AssetExecutionContext) Dagster context + atproto_resource (ATProtoResource) Resource for interfacing with atmosphere protocol + s3_resource (S3Resource) Resource for uploading files to S3 storage + + """ + atproto_client = atproto_resource.get_client() + + starter_pack_uri = context.partition_key + + list_items = get_all_starter_pack_members(atproto_client, starter_pack_uri) + + _bytes = os.linesep.join([member.model_dump_json() for member in list_items]).encode("utf-8") + + datetime_now = datetime.now() + object_key = "/".join( + ( + "atproto_starter_pack_snapshot", + datetime_now.strftime("%Y-%m-%d"), + datetime_now.strftime("%H"), + datetime_now.strftime("%M"), + f"{starter_pack_uri}.json", + ) + ) + + s3_resource.get_client().put_object(Body=_bytes, Bucket=AWS_BUCKET_NAME, Key=object_key) + + context.instance.add_dynamic_partitions( + partitions_def_name="atproto_did_dynamic_partition", + partition_keys=[list_item_view.subject.did for list_item_view in list_items], + ) + + return dg.MaterializeResult( + metadata={ + "len_members": len(list_items), + "s3_object_key": object_key, + } + ) + + +@dg.asset( + partitions_def=atproto_did_dynamic_partition, + deps=[dg.AssetDep(starter_pack_snapshot, partition_mapping=dg.AllPartitionMapping())], + automation_condition=dg.AutomationCondition.eager(), + kinds={"python"}, + group_name="ingestion", + op_tags={"dagster/concurrency_key": "ingestion"}, +) +def actor_feed_snapshot( + context: dg.AssetExecutionContext, + atproto_resource: ATProtoResource, + s3_resource: S3Resource, +) -> dg.MaterializeResult: + """Snapshot of full user feed written to S3 storage.""" + client = atproto_resource.get_client() + actor_did = context.partition_key + + # NOTE: we may need to yield chunks to be more memory efficient + items = get_all_feed_items(client, actor_did) + + datetime_now = datetime.now() + + object_key = "/".join( + ( + "atproto_actor_feed_snapshot", + datetime_now.strftime("%Y-%m-%d"), + datetime_now.strftime("%H"), + datetime_now.strftime("%M"), + f"{actor_did}.json", + ) + ) + + _bytes = os.linesep.join([item.model_dump_json() for item in items]).encode("utf-8") + + s3_resource.get_client().put_object(Body=_bytes, Bucket=AWS_BUCKET_NAME, Key=object_key) + + return dg.MaterializeResult( + metadata={ + "len_feed_items": len(items), + "s3_object_key": object_key, + } + ) + + +atproto_resource = ATProtoResource( + login=dg.EnvVar("BSKY_LOGIN"), password=dg.EnvVar("BSKY_APP_PASSWORD") +) + +s3_resource = S3Resource( + endpoint_url=dg.EnvVar("AWS_ENDPOINT_URL"), + aws_access_key_id=dg.EnvVar("AWS_ACCESS_KEY_ID"), + aws_secret_access_key=dg.EnvVar("AWS_SECRET_ACCESS_KEY"), + region_name="auto", +) + + +defs = dg.Definitions( + assets=[starter_pack_snapshot, actor_feed_snapshot], + resources={ + "atproto_resource": atproto_resource, + "s3_resource": s3_resource, + }, +) diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/resources.py b/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/resources.py new file mode 100644 index 0000000000000..38163e85896df --- /dev/null +++ b/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/resources.py @@ -0,0 +1,29 @@ +import os + +import dagster as dg +from atproto import Client + + +class ATProtoResource(dg.ConfigurableResource): + login: str + password: str + session_cache_path: str = "atproto-session.txt" + + def _login(self, client): + """Create a re-usable session to be used across resource instances; we are rate limited to 30/5 minutes or 300/day session.""" + if os.path.exists(self.session_cache_path): + with open(self.session_cache_path, "r") as f: + session_string = f.read() + client.login(session_string=session_string) + else: + client.login(login=self.login, password=self.password) + session_string = client.export_session_string() + with open(self.session_cache_path, "w") as f: + f.write(session_string) + + def get_client( + self, + ) -> Client: + client = Client() + self._login(client) + return client diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/utils/__init__.py b/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/utils/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/utils/atproto.py b/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/utils/atproto.py new file mode 100644 index 0000000000000..fe8fadb7e857a --- /dev/null +++ b/examples/project_atproto_dashboard/project_atproto_dashboard/ingestion/utils/atproto.py @@ -0,0 +1,59 @@ +from typing import TYPE_CHECKING, List, Optional + +from atproto import Client + +if TYPE_CHECKING: + from atproto_client import models + + +def get_all_feed_items(client: Client, actor: str) -> List["models.AppBskyFeedDefs.FeedViewPost"]: + """Retrieves all author feed items for a given `actor`. + + Args: + client (Client): AT Protocol client + actor (str): author identifier (did) + + Returns: + List['models.AppBskyFeedDefs.FeedViewPost'] list of feed + + """ + import math + + import tenacity + + @tenacity.retry( + stop=tenacity.stop_after_attempt(5), + wait=tenacity.wait_fixed(math.ceil(60 * 2.5)), + ) + def _get_feed_with_retries(client: Client, actor: str, cursor: Optional[str]): + return client.get_author_feed(actor=actor, cursor=cursor, limit=100) + + feed = [] + cursor = None + while True: + data = _get_feed_with_retries(client, actor, cursor) + feed.extend(data.feed) + cursor = data.cursor + if not cursor: + break + + return feed + + +def get_all_list_members(client: Client, list_uri: str): + cursor = None + members = [] + while True: + response = client.app.bsky.graph.get_list( + {"list": list_uri, "cursor": cursor, "limit": 100} + ) + members.extend(response.items) + if not response.cursor: + break + cursor = response.cursor + return members + + +def get_all_starter_pack_members(client: Client, starter_pack_uri: str): + response = client.app.bsky.graph.get_starter_pack({"starter_pack": starter_pack_uri}) + return get_all_list_members(client, response.starter_pack.list.uri) diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard/modeling/__init__.py b/examples/project_atproto_dashboard/project_atproto_dashboard/modeling/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard/modeling/definitions.py b/examples/project_atproto_dashboard/project_atproto_dashboard/modeling/definitions.py new file mode 100644 index 0000000000000..6e7acf2d55f78 --- /dev/null +++ b/examples/project_atproto_dashboard/project_atproto_dashboard/modeling/definitions.py @@ -0,0 +1,45 @@ +import os +from pathlib import Path +from typing import Any, Mapping, Optional + +import dagster as dg +from dagster_dbt import DagsterDbtTranslator, DbtCliResource, DbtProject, dbt_assets + +dbt_project = DbtProject( + project_dir=Path(__file__).joinpath("..", "..", "..", "dbt_project").resolve(), + target=os.getenv("DBT_TARGET"), +) +dbt_project.prepare_if_dev() +dbt_resource = DbtCliResource(project_dir=dbt_project) + + +class CustomizedDagsterDbtTranslator(DagsterDbtTranslator): + def get_group_name(self, dbt_resource_props: Mapping[str, Any]) -> Optional[str]: + asset_path = dbt_resource_props["fqn"][1:-1] + if asset_path: + return "_".join(asset_path) + return "default" + + def get_asset_key(self, dbt_resource_props): + resource_type = dbt_resource_props["resource_type"] + name = dbt_resource_props["name"] + if resource_type == "source": + return dg.AssetKey(name) + else: + return super().get_asset_key(dbt_resource_props) + + +@dbt_assets( + manifest=dbt_project.manifest_path, + dagster_dbt_translator=CustomizedDagsterDbtTranslator(), +) +def dbt_bluesky(context: dg.AssetExecutionContext, dbt: DbtCliResource): + yield from (dbt.cli(["build"], context=context).stream().fetch_row_counts()) + + +defs = dg.Definitions( + assets=[dbt_bluesky], + resources={ + "dbt": dbt_resource, + }, +) diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard_tests/__init__.py b/examples/project_atproto_dashboard/project_atproto_dashboard_tests/__init__.py new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/examples/project_atproto_dashboard/project_atproto_dashboard_tests/__init__.py @@ -0,0 +1 @@ + diff --git a/examples/project_atproto_dashboard/project_atproto_dashboard_tests/test_assets.py b/examples/project_atproto_dashboard/project_atproto_dashboard_tests/test_assets.py new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/examples/project_atproto_dashboard/project_atproto_dashboard_tests/test_assets.py @@ -0,0 +1 @@ + diff --git a/examples/project_atproto_dashboard/pyproject.toml b/examples/project_atproto_dashboard/pyproject.toml new file mode 100644 index 0000000000000..068f8e743f8a5 --- /dev/null +++ b/examples/project_atproto_dashboard/pyproject.toml @@ -0,0 +1,34 @@ +[project] +name = "project_atproto_dashboard" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.9,<3.13" +dependencies = [ + "atproto", + "dagster", + "dagster-aws", + "dagster-dbt", + "dagster-duckdb", + "dagster-powerbi", + "dbt-duckdb", + "tenacity", +] + +[project.optional-dependencies] +dev = [ + "dagster-webserver", + "pytest", + "ruff", +] + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[tool.dagster] +module_name = "project_atproto_dashboard.definitions" +project_name = "project_atproto_dashboard" + +[tool.setuptools.packages.find] +exclude=["project_atproto_dashboard_tests"] diff --git a/pyright/alt-1/requirements-pinned.txt b/pyright/alt-1/requirements-pinned.txt index 6865024a2edaf..9c55cd90ee281 100644 --- a/pyright/alt-1/requirements-pinned.txt +++ b/pyright/alt-1/requirements-pinned.txt @@ -4,7 +4,7 @@ aiofile==3.9.0 aiohappyeyeballs==2.4.4 aiohttp==3.11.10 aioitertools==0.12.0 -aiosignal==1.3.1 +aiosignal==1.3.2 alembic==1.14.0 annotated-types==0.7.0 antlr4-python3-runtime==4.13.2 @@ -18,7 +18,7 @@ asn1crypto==1.5.1 astroid==3.3.6 asttokens==3.0.0 async-lru==2.0.4 -attrs==24.2.0 +attrs==24.3.0 babel==2.16.0 backoff==2.2.1 backports-tarfile==1.2.0 @@ -27,11 +27,11 @@ bleach==6.2.0 boto3==1.35.36 boto3-stubs-lite==1.35.70 botocore==1.35.36 -botocore-stubs==1.35.78 +botocore-stubs==1.35.82 buildkite-test-collector==0.1.9 cachetools==5.5.0 caio==0.9.17 -certifi==2024.8.30 +certifi==2024.12.14 cffi==1.17.1 chardet==5.2.0 charset-normalizer==3.4.0 @@ -41,7 +41,7 @@ coloredlogs==14.0 comm==0.2.2 contourpy==1.3.1 coverage==7.6.9 -croniter==3.0.4 +croniter==5.0.1 cryptography==44.0.0 cycler==0.12.1 daff==1.3.46 @@ -73,7 +73,7 @@ dbt-duckdb==1.9.1 dbt-extractor==0.5.1 dbt-semantic-interfaces==0.5.1 dbt-snowflake==1.9.0 -debugpy==1.8.9 +debugpy==1.8.11 decopatch==1.4.10 decorator==5.1.1 deepdiff==7.0.1 @@ -95,8 +95,8 @@ frozenlist==1.5.0 fsspec==2024.3.0 gcsfs==0.8.0 google-api-core==2.24.0 -google-api-python-client==2.154.0 -google-auth==2.36.0 +google-api-python-client==2.155.0 +google-auth==2.37.0 google-auth-httplib2==0.2.0 google-auth-oauthlib==1.2.1 google-cloud-bigquery==3.27.0 @@ -141,7 +141,7 @@ jsonschema==4.23.0 jsonschema-specifications==2024.10.1 jupyter-client==8.6.3 jupyter-core==5.7.2 -jupyter-events==0.10.0 +jupyter-events==0.11.0 jupyter-lsp==2.2.5 jupyter-server==2.14.2 jupyter-server-terminals==0.5.3 @@ -157,7 +157,7 @@ mako==1.3.8 markdown-it-py==3.0.0 markupsafe==3.0.2 mashumaro==3.14 -matplotlib==3.9.3 +matplotlib==3.10.0 matplotlib-inline==0.1.7 mccabe==0.7.0 mdurl==0.1.2 @@ -171,10 +171,10 @@ multimethod==1.12 mypy==1.13.0 mypy-boto3-ecs==1.35.77 mypy-boto3-emr==1.35.68 -mypy-boto3-emr-serverless==1.35.25 -mypy-boto3-glue==1.35.74 -mypy-boto3-logs==1.35.72 -mypy-boto3-s3==1.35.76.post1 +mypy-boto3-emr-serverless==1.35.79 +mypy-boto3-glue==1.35.80 +mypy-boto3-logs==1.35.81 +mypy-boto3-s3==1.35.81 mypy-extensions==1.0.0 mypy-protobuf==3.6.0 nbclient==0.10.1 @@ -231,14 +231,14 @@ pyproject-api==1.8.0 pyright==1.1.379 pyspark==3.5.3 pytest==8.3.4 -pytest-asyncio==0.24.0 +pytest-asyncio==0.25.0 pytest-cases==3.8.6 pytest-cov==5.0.0 pytest-mock==3.14.0 pytest-xdist==3.6.1 python-dateutil==2.9.0.post0 python-dotenv==1.0.1 -python-json-logger==2.0.7 +python-json-logger==3.2.1 python-slugify==8.0.4 pytimeparse==1.1.8 pytz==2024.2 @@ -275,7 +275,7 @@ sqlglot==26.0.0 sqlglotrs==0.3.0 sqlparse==0.5.3 stack-data==0.6.3 -starlette==0.41.3 +starlette==0.42.0 structlog==24.4.0 syrupy==4.8.0 tabulate==0.9.0 @@ -292,7 +292,7 @@ tqdm==4.67.1 traitlets==5.14.3 typeguard==4.4.1 typer==0.15.1 -types-awscrt==0.23.4 +types-awscrt==0.23.5 types-backports==0.1.3 types-certifi==2021.10.8.3 types-cffi==1.16.0.20240331 @@ -318,11 +318,11 @@ typing-extensions==4.12.2 typing-inspect==0.9.0 tzdata==2024.2 ujson==5.10.0 -universal-pathlib==0.2.5 +universal-pathlib==0.2.6 uri-template==1.3.0 uritemplate==4.1.1 urllib3==2.2.3 -uvicorn==0.32.1 +uvicorn==0.34.0 uvloop==0.21.0 virtualenv==20.28.0 watchdog==5.0.3 diff --git a/pyright/master/requirements-pinned.txt b/pyright/master/requirements-pinned.txt index 18552e740a2a3..f3383f88c3e96 100644 --- a/pyright/master/requirements-pinned.txt +++ b/pyright/master/requirements-pinned.txt @@ -5,7 +5,7 @@ aiohappyeyeballs==2.4.4 aiohttp==3.10.11 aiohttp-retry==2.8.3 aioresponses==0.7.7 -aiosignal==1.3.1 +aiosignal==1.3.2 alabaster==1.0.0 alembic==1.14.0 altair==4.2.2 @@ -36,7 +36,8 @@ asn1crypto==1.5.1 -e examples/assets_pandas_pyspark asttokens==3.0.0 async-lru==2.0.4 -attrs==24.2.0 +atproto==0.0.56 +attrs==24.3.0 autodocsumm==0.2.14 autoflake==2.3.1 -e python_modules/automation @@ -57,10 +58,10 @@ billiard==4.2.1 bleach==6.2.0 blinker==1.9.0 bokeh==3.6.2 -boto3==1.35.78 +boto3==1.35.82 boto3-stubs-lite==1.35.70 -botocore==1.35.78 -botocore-stubs==1.35.78 +botocore==1.35.82 +botocore-stubs==1.35.82 buildkite-test-collector==0.1.9 cachecontrol==0.14.1 cached-property==2.0.1 @@ -70,9 +71,9 @@ caio==0.9.17 callee==0.3.1 cattrs==23.1.2 celery==5.4.0 -certifi==2024.8.30 +certifi==2024.12.14 cffi==1.17.1 -cfn-lint==1.22.0 +cfn-lint==1.22.2 chardet==5.2.0 charset-normalizer==3.4.0 click==8.1.7 @@ -93,8 +94,8 @@ connexion==2.14.2 contourpy==1.3.1 coverage==7.6.9 cron-descriptor==1.4.5 -croniter==3.0.4 -cryptography==44.0.0 +croniter==5.0.1 +cryptography==43.0.3 cssselect==1.2.0 cssutils==2.11.1 cycler==0.12.1 @@ -121,6 +122,7 @@ dagster-contrib-modal==0.0.2 -e python_modules/libraries/dagster-deltalake -e python_modules/libraries/dagster-deltalake-pandas -e python_modules/libraries/dagster-deltalake-polars +-e python_modules/libraries/dagster-dg -e examples/experimental/dagster-dlift -e python_modules/libraries/dagster-docker -e python_modules/libraries/dagster-duckdb @@ -189,7 +191,7 @@ dbt-duckdb==1.9.1 -e examples/starlift-demo dbt-extractor==0.5.1 dbt-semantic-interfaces==0.5.1 -debugpy==1.8.9 +debugpy==1.8.11 decopatch==1.4.10 decorator==5.1.1 deepdiff==7.0.1 @@ -221,7 +223,7 @@ execnet==2.1.1 executing==2.1.0 expandvars==0.12.0 faiss-cpu==1.8.0 -fastapi==0.115.6 +fastapi==0.1.17 fastavro==1.9.7 fastjsonschema==2.21.1 -e examples/feature_graph_backed_assets @@ -250,8 +252,8 @@ gitdb==4.0.11 gitpython==3.1.43 giturlparse==0.12.0 google-api-core==2.24.0 -google-api-python-client==2.154.0 -google-auth==2.36.0 +google-api-python-client==2.155.0 +google-auth==2.37.0 google-auth-httplib2==0.2.0 google-auth-oauthlib==1.2.1 google-cloud-bigquery==3.27.0 @@ -281,7 +283,7 @@ html5lib==1.1 httpcore==1.0.7 httplib2==0.22.0 httptools==0.6.4 -httpx==0.28.1 +httpx==0.27.2 httpx-sse==0.4.0 humanfriendly==10.0 humanize==4.11.0 @@ -321,7 +323,7 @@ jsonschema-path==0.3.3 jsonschema-specifications==2023.12.1 jupyter-client==7.4.9 jupyter-core==5.7.2 -jupyter-events==0.10.0 +jupyter-events==0.11.0 jupyter-lsp==2.2.5 jupyter-server==2.14.2 jupyter-server-terminals==0.5.3 @@ -333,17 +335,18 @@ keyring==25.5.0 -e python_modules/libraries/dagster-airlift/kitchen-sink kiwisolver==1.4.7 kombu==5.4.2 -kopf==1.37.3 +kopf==1.37.4 kubernetes==31.0.0 kubernetes-asyncio==31.1.1 langchain==0.3.7 langchain-community==0.3.5 -langchain-core==0.3.24 +langchain-core==0.3.25 langchain-openai==0.2.5 -langchain-text-splitters==0.3.2 +langchain-text-splitters==0.3.3 langsmith==0.1.147 lazy-object-proxy==1.10.0 leather==0.4.0 +libipld==3.0.0 limits==3.14.1 linkify-it-py==2.0.3 lkml==1.3.6 @@ -361,7 +364,7 @@ marshmallow==3.23.1 marshmallow-oneofschema==3.1.1 marshmallow-sqlalchemy==0.26.1 mashumaro==3.15 -matplotlib==3.9.3 +matplotlib==3.10.0 matplotlib-inline==0.1.3 mbstrdecoder==1.1.3 mdit-py-plugins==0.4.2 @@ -370,7 +373,7 @@ minimal-snowplow-tracker==0.0.2 mistune==3.0.2 mixpanel==4.10.1 mlflow==1.27.0 -modal==0.67.46 +modal==0.68.26 more-itertools==10.5.0 morefs==0.2.2 moto==4.2.14 @@ -382,10 +385,10 @@ multidict==6.1.0 multimethod==1.12 mypy-boto3-ecs==1.35.77 mypy-boto3-emr==1.35.68 -mypy-boto3-emr-serverless==1.35.25 -mypy-boto3-glue==1.35.74 -mypy-boto3-logs==1.35.72 -mypy-boto3-s3==1.35.76.post1 +mypy-boto3-emr-serverless==1.35.79 +mypy-boto3-glue==1.35.80 +mypy-boto3-logs==1.35.81 +mypy-boto3-s3==1.35.81 mypy-extensions==1.0.0 mypy-protobuf==3.6.0 mysql-connector-python==9.1.0 @@ -395,7 +398,7 @@ nbconvert==7.16.4 nbformat==5.10.4 nest-asyncio==1.6.0 networkx==3.4.2 -nh3==0.2.19 +nh3==0.2.20 nodeenv==1.9.1 notebook==7.3.1 notebook-shim==0.2.4 @@ -406,7 +409,7 @@ objgraph==3.6.2 onnx==1.17.0 onnxconverter-common==1.13.0 onnxruntime==1.20.1 -openai==1.57.2 +openai==1.57.4 openapi-schema-validator==0.6.2 openapi-spec-validator==0.7.1 opentelemetry-api==1.29.0 @@ -451,6 +454,7 @@ portalocker==2.10.1 premailer==3.10.0 prison==0.2.1 progressbar2==4.5.0 +-e examples/project_atproto_dashboard -e examples/project_dagster_modal_pipes prometheus-client==0.21.1 prometheus-flask-exporter==0.23.1 @@ -471,7 +475,7 @@ pyasn1-modules==0.4.1 pycparser==2.22 pydantic==2.10.3 pydantic-core==2.27.1 -pydantic-settings==2.6.1 +pydantic-settings==2.7.0 pydata-google-auth==1.9.0 pyflakes==3.2.0 pygments==2.18.0 @@ -487,7 +491,7 @@ pysocks==1.7.1 pyspark==3.5.3 pytablereader==0.31.4 pytest==8.3.4 -pytest-asyncio==0.24.0 +pytest-asyncio==0.25.0 pytest-cases==3.8.6 pytest-cov==5.0.0 pytest-mock==3.14.0 @@ -497,9 +501,9 @@ python-dateutil==2.9.0.post0 python-dotenv==1.0.1 python-frontmatter==1.1.0 python-jose==3.3.0 -python-json-logger==2.0.7 +python-json-logger==3.2.1 python-liquid==1.12.1 -python-multipart==0.0.19 +python-multipart==0.0.20 python-nvd3==0.16.0 python-slugify==8.0.4 python-utils==3.9.1 @@ -529,7 +533,7 @@ rpds-py==0.22.3 rsa==4.9 ruamel-yaml==0.17.40 ruamel-yaml-clib==0.2.12 -ruff==0.8.2 +ruff==0.8.3 s3transfer==0.10.4 scikit-learn==1.6.0 scipy==1.14.1 @@ -550,8 +554,8 @@ six==1.17.0 skein==0.8.2 skl2onnx==1.17.0 slack-sdk==3.33.5 -sling==1.3.2 -sling-mac-arm64==1.3.2 +sling==1.3.3 +sling-mac-arm64==1.3.3 smmap==5.0.1 sniffio==1.3.1 snowballstemmer==2.2.0 @@ -580,10 +584,10 @@ sqlparse==0.5.3 sshpubkeys==3.3.1 sshtunnel==0.4.0 stack-data==0.6.3 -starlette==0.41.3 +starlette==0.42.0 structlog==24.4.0 sympy==1.13.1 -synchronicity==0.9.5 +synchronicity==0.9.6 syrupy==4.8.0 tableauserverclient==0.34 tabledata==1.3.3 @@ -611,12 +615,12 @@ trio==0.27.0 trio-websocket==0.11.1 -e examples/airlift-migration-tutorial -e examples/tutorial_notebook_assets -twilio==9.3.8 +twilio==9.4.1 twine==6.0.1 typeguard==4.4.1 typepy==1.3.2 typer==0.15.1 -types-awscrt==0.23.4 +types-awscrt==0.23.5 types-backports==0.1.3 types-certifi==2021.10.8.3 types-cffi==1.16.0.20240331 @@ -645,23 +649,23 @@ tzdata==2024.2 tzlocal==5.2 uc-micro-py==1.0.3 unicodecsv==0.14.1 -universal-pathlib==0.2.5 +universal-pathlib==0.2.6 uri-template==1.3.0 uritemplate==4.1.1 urllib3==2.2.3 -e examples/use_case_repository -uvicorn==0.32.1 +uvicorn==0.34.0 uvloop==0.21.0 vine==5.1.0 virtualenv==20.28.0 -wandb==0.19.0 +wandb==0.19.1 watchdog==5.0.3 watchfiles==1.0.3 wcwidth==0.2.13 webcolors==24.11.1 webencodings==0.5.1 websocket-client==1.8.0 -websockets==14.1 +websockets==13.1 werkzeug==2.2.3 wheel==0.45.1 widgetsnbextension==4.0.13 diff --git a/pyright/master/requirements.txt b/pyright/master/requirements.txt index 066776fc53a73..1d9b6d64ff895 100644 --- a/pyright/master/requirements.txt +++ b/pyright/master/requirements.txt @@ -142,4 +142,5 @@ types-sqlalchemy==1.4.53.34 -e python_modules/libraries/dagster-airlift/perf-harness -e examples/airlift-migration-tutorial -e examples/use_case_repository[dev] +-e examples/project_atproto_dashboard -e examples/project_dagster_modal_pipes