diff --git a/.github/workflows/build-warehouse-image.yml b/.github/workflows/build-warehouse-image.yml index 8ea2f37934..c645d091c4 100644 --- a/.github/workflows/build-warehouse-image.yml +++ b/.github/workflows/build-warehouse-image.yml @@ -6,11 +6,19 @@ on: - 'main' paths: - '.github/workflows/build-warehouse-image.yml' - - 'warehouse/**' + - 'warehouse/Dockerfile' + - 'warehouse/packages.yml' + - 'warehouse/poetry.lock' + - 'warehouse/pyproject.yml' + - 'warehouse/requirements.txt' pull_request: paths: - '.github/workflows/build-warehouse-image.yml' - - 'warehouse/**' + - 'warehouse/Dockerfile' + - 'warehouse/packages.yml' + - 'warehouse/poetry.lock' + - 'warehouse/pyproject.yml' + - 'warehouse/requirements.txt' concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/warehouse/models/intermediate/gtfs/_int_gtfs.yaml b/warehouse/models/intermediate/gtfs/_int_gtfs.yaml index 5e622b2728..75d64c8037 100644 --- a/warehouse/models/intermediate/gtfs/_int_gtfs.yaml +++ b/warehouse/models/intermediate/gtfs/_int_gtfs.yaml @@ -88,30 +88,39 @@ models: - name: int_gtfs_schedule__long_calendar description: | This table transforms the raw GTFS calendar.txt format (where each row corresponds to a `service_id` and - each day of the week is a column and service indicators are entered in a "wide" fashion) into a long format, - where a row is identified by `feed_key`, `service_id`, and `day_name`. - tests: - - dbt_utils.unique_combination_of_columns: - combination_of_columns: - - feed_key - - service_id - - day_name + each day of the week is a column and service indicators are entered in a "wide" fashion) + into a long format, where a row is identified by `feed_key`, `service_id`, and `date`. columns: - - name: base64_url + - &schedule_key + name: key + description: + Synthetic key from `service_id`, `feed_key`, and `service_date`. + tests: + - unique + - not_null - name: feed_key description: Foreign key for `dim_schedule_feeds`. - name: service_id description: '{{ doc("gtfs_calendar__service_id") }}' - - name: start_date - description: '{{ doc("gtfs_calendar__start_date") }}' - - name: end_date - description: '{{ doc("gtfs_calendar_dates__date") }}' - - name: day_name - description: String name of day of the week like "monday" - - name: has_service + - name: service_date + description: | + Date on which this service was active (i.e., this date is betweem the + `start_date` and `end_date` for this service). + - name: day_num description: | - Boolean indicating whether there is service for this `service_id` / `day_name` pair - between `start_date` and `end_date`. + Day of week as number (Sunday = 1, Saturday = 7). + - name: service_bool + description: | + Boolean indicating whether there is service for this `service_id` / `date` pair. + tests: + - not_null + - name: calendar_key + description: | + Foreign key to dim_calendar. + tests: + - relationships: + to: ref('dim_calendar') + field: key - name: int_gtfs_schedule__daily_scheduled_service_index description: | An index listing date, feed, and `service_id` combinations for which service was scheduled (i.e., @@ -141,6 +150,22 @@ models: description: Foreign key for `dim_schedule_feeds`. tests: - not_null + - name: calendar_key + description: | + Foreign key for `dim_calendar`. If null, the service for this date was + defined exclusively via `calendar_dates`. + tests: + - relationships: + to: ref('dim_calendar') + field: key + - name: calendar_dates_key + description: | + Foreign key for `dim_calendar_dates`. If null, the service for this date was + defined exclusively via `calendar`. + tests: + - relationships: + to: ref('dim_calendar_dates') + field: key - name: service_date description: Date on which service was scheduled. tests: @@ -375,3 +400,51 @@ models: RT data type: "GTFS Alerts", "GTFS VehiclePositions", "GTFS TripUpdates". tests: - not_null + - name: int_gtfs_schedule__all_scheduled_service + description: | + **Use with caution: This table lists service for all feeds, regardless of whether the given feed + was actually "active" on the given day. To see only service for active feeds, + consult `int_gtfs_schedule__daily_scheduled_service_index`.** + Lists date, feed, and `service_id` combinations for which service was scheduled (i.e., + the `service_id` is "in effect" and says that service occurred). + Essentially, it takes `calendar` and `calendar_dates` for a given feed, takes the dates for which that + feed was "in effect", and combines those into a long list of all service_ids that were in effect for a given + date, then filters down to only those where service was actually scheduled on that date. + For example, a row in this table with `feed_key = A`, `service_date = 2022-10-01`, `service_id = 1` indicates + that: + * Service ID 1 covers the date 2022-10-01, i.e., if service ID 1 is defined in `calendar.txt`, + `start_date <= 2022-10-01 <= end_date` and if service ID 1 is defined in `calendar_dates.txt`, + `2022-10-01` is listed as a `date` within that file. + * The service indicator is `true` for 2022-10-01 (a Saturday). So, if this service was defined in `calendar.txt`, + `saturday = 1` for `service_id = 1`, and there is no `exception_type = 2` in `calendar_dates.txt` for this service and date. + If this service is defined exclusively in `calendar_dates.txt`, then `exception_type = 1` is listed for `2022-10-01` in that file. + columns: + - *schedule_key + - name: feed_key + description: Foreign key for `dim_schedule_feeds`. + tests: + - not_null + - name: calendar_key + description: | + Foreign key for `dim_calendar`. If null, the service for this date was + defined exclusively via `calendar_dates`. + tests: + - relationships: + to: ref('dim_calendar') + field: key + - name: calendar_dates_key + description: | + Foreign key for `dim_calendar_dates`. If null, the service for this date was + defined exclusively via `calendar`. + tests: + - relationships: + to: ref('dim_calendar_dates') + field: key + - name: service_date + description: Date on which service was scheduled. + tests: + - not_null + - name: service_id + description: Service identifier from calendar and/or calendar_dates. + tests: + - not_null diff --git a/warehouse/models/intermediate/gtfs/int_gtfs_schedule__all_scheduled_service.sql b/warehouse/models/intermediate/gtfs/int_gtfs_schedule__all_scheduled_service.sql new file mode 100644 index 0000000000..c5ff169500 --- /dev/null +++ b/warehouse/models/intermediate/gtfs/int_gtfs_schedule__all_scheduled_service.sql @@ -0,0 +1,58 @@ +{{ config(materialized='table') }} + +WITH dim_calendar_dates AS ( + SELECT * + FROM {{ ref('dim_calendar_dates') }} +), + +int_gtfs_schedule__long_calendar AS ( + SELECT * + FROM {{ ref('int_gtfs_schedule__long_calendar') }} +), + +boolean_calendar_dates AS ( + SELECT + -- at time of writing, this will be identical to `calendar_dates_key`, but just in case? + {{ dbt_utils.surrogate_key(['feed_key', 'service_id', 'date']) }} AS key, + date AS service_date, + feed_key, + key AS calendar_dates_key, + service_id, + CASE + WHEN exception_type = 1 THEN TRUE + WHEN exception_type = 2 THEN FALSE + END AS service_bool + FROM dim_calendar_dates +), + +daily_services AS ( + SELECT + -- these values will be identical so doesn't matter which is first in coalesce + COALESCE(long_cal.key, cal_dates.key) AS key, + COALESCE(long_cal.service_date, cal_dates.service_date) AS service_date, + COALESCE(long_cal.feed_key, cal_dates.feed_key) AS feed_key, + COALESCE(long_cal.service_id, cal_dates.service_id) AS service_id, + -- calendar_dates takes precedence if present: it can modify calendar + -- if no calendar_dates, use calendar + -- if neither, no service + COALESCE(cal_dates.service_bool, long_cal.service_bool) AS service_bool, + calendar_key, + calendar_dates_key + FROM int_gtfs_schedule__long_calendar AS long_cal + FULL OUTER JOIN boolean_calendar_dates AS cal_dates + USING (key) +), + +int_gtfs_schedule__all_scheduled_service AS ( + SELECT + key, + service_date, + feed_key, + calendar_key, + calendar_dates_key, + service_id + FROM daily_services + WHERE service_bool +) + +SELECT * FROM int_gtfs_schedule__all_scheduled_service diff --git a/warehouse/models/intermediate/gtfs/int_gtfs_schedule__daily_scheduled_service_index.sql b/warehouse/models/intermediate/gtfs/int_gtfs_schedule__daily_scheduled_service_index.sql index d2e37278d5..5e32150cd1 100644 --- a/warehouse/models/intermediate/gtfs/int_gtfs_schedule__daily_scheduled_service_index.sql +++ b/warehouse/models/intermediate/gtfs/int_gtfs_schedule__daily_scheduled_service_index.sql @@ -1,83 +1,28 @@ {{ config(materialized='table') }} -WITH dim_calendar_dates AS ( - SELECT * - FROM {{ ref('dim_calendar_dates') }} -), - -int_gtfs_schedule__long_calendar AS ( - SELECT * - FROM {{ ref('int_gtfs_schedule__long_calendar') }} -), - -fct_daily_schedule_feeds AS ( +WITH fct_daily_schedule_feeds AS ( SELECT *, EXTRACT(DAYOFWEEK FROM date) AS day_num FROM {{ ref('fct_daily_schedule_feeds') }} ), -boolean_calendar_dates AS ( - SELECT - date, - feed_key, - service_id, - CASE - WHEN exception_type = 1 THEN TRUE - WHEN exception_type = 2 THEN FALSE - END AS has_service - FROM dim_calendar_dates -), - --- decide that exception type 2 trumps exception type 1 --- i.e., if same date appears twice with two exception types --- the cancelation wins and we say no service on that date --- (this generally shouldn't happen) -summarize_calendar_dates AS ( - SELECT - date, - feed_key, - service_id, - LOGICAL_AND(has_service) AS has_service - FROM boolean_calendar_dates - GROUP BY date, feed_key, service_id -), - -daily_services AS ( - - SELECT - daily_feeds.date AS service_date, - cal_dates.date AS cd_date, - daily_feeds.feed_key, - long_cal.service_id AS calendar_service_id, - long_cal.has_service AS calendar_has_service, - cal_dates.service_id AS calendar_dates_service_id, - cal_dates.has_service AS calendar_dates_has_service, - COALESCE(long_cal.service_id, cal_dates.service_id) AS service_id, - -- calendar_dates takes precedence if present: it can modify calendar - -- if no calendar_dates, use calendar - -- if neither, no service - COALESCE(cal_dates.has_service, long_cal.has_service, FALSE) AS has_service - FROM fct_daily_schedule_feeds AS daily_feeds - LEFT JOIN int_gtfs_schedule__long_calendar AS long_cal - ON daily_feeds.feed_key = long_cal.feed_key - AND daily_feeds.day_num = long_cal.day_num - AND daily_feeds.date BETWEEN long_cal.start_date AND long_cal.end_date - LEFT JOIN summarize_calendar_dates AS cal_dates - ON daily_feeds.feed_key = cal_dates.feed_key - AND daily_feeds.date = cal_dates.date - AND (long_cal.service_id = cal_dates.service_id OR long_cal.service_id IS NULL) +all_scheduled_service AS ( + SELECT * + FROM {{ ref('int_gtfs_schedule__all_scheduled_service') }} ), int_gtfs_schedule__daily_scheduled_service_index AS ( SELECT service_date, - cd_date, - feed_key, - service_id - FROM daily_services - WHERE service_id IS NOT NULL - AND has_service + fct_daily_schedule_feeds.feed_key, + service_id, + calendar_key, + calendar_dates_key + FROM all_scheduled_service + INNER JOIN fct_daily_schedule_feeds + ON all_scheduled_service.feed_key = fct_daily_schedule_feeds.feed_key + AND all_scheduled_service.service_date = fct_daily_schedule_feeds.date ) SELECT * FROM int_gtfs_schedule__daily_scheduled_service_index diff --git a/warehouse/models/intermediate/gtfs/int_gtfs_schedule__long_calendar.sql b/warehouse/models/intermediate/gtfs/int_gtfs_schedule__long_calendar.sql index a65e4bbbf1..d9a61c3ef3 100644 --- a/warehouse/models/intermediate/gtfs/int_gtfs_schedule__long_calendar.sql +++ b/warehouse/models/intermediate/gtfs/int_gtfs_schedule__long_calendar.sql @@ -1,30 +1,32 @@ +{{ config(materialized='table') }} + -- TODO: make an intermediate calendar and use that instead of the dimension WITH dim_calendar AS ( SELECT * FROM {{ ref('dim_calendar') }} ), --- TODO: see if this can be refactored using UNPIVOT (logic inherited from v1 warehouse, wondering if it should be revisited) int_gtfs_schedule__long_calendar AS ( - -- Note that you can unnest values easily in SQL, but getting the column names - -- is weirdly hard. To work around this, we just UNION ALL. - {% for dow in [("monday", 2), ("tuesday", 3), ("wednesday", 4), ("thursday", 5), ("friday", 6), ("saturday", 7), ("sunday", 0)] %} - - {% if not loop.first %} - UNION ALL - {% endif %} - - SELECT - base64_url, - feed_key, - service_id, - start_date, - end_date, - "{{ dow[0] }}" AS day_name, - {{ dow[1] }} AS day_num, - CAST({{ dow[0] }} AS boolean) AS has_service - FROM dim_calendar - {% endfor %} + SELECT + feed_key, + {{ dbt_utils.surrogate_key(['feed_key', 'service_id', 'dt']) }} AS key, + service_id, + dt AS service_date, + EXTRACT(DAYOFWEEK FROM dt) AS day_num, + CASE + WHEN EXTRACT(DAYOFWEEK FROM dt) = 1 THEN CAST(sunday AS bool) + WHEN EXTRACT(DAYOFWEEK FROM dt) = 2 THEN CAST(monday AS bool) + WHEN EXTRACT(DAYOFWEEK FROM dt) = 3 THEN CAST(tuesday AS bool) + WHEN EXTRACT(DAYOFWEEK FROM dt) = 4 THEN CAST(wednesday AS bool) + WHEN EXTRACT(DAYOFWEEK FROM dt) = 5 THEN CAST(thursday AS bool) + WHEN EXTRACT(DAYOFWEEK FROM dt) = 6 THEN CAST(friday AS bool) + WHEN EXTRACT(DAYOFWEEK FROM dt) = 7 THEN CAST(saturday AS bool) + END AS service_bool, + key AS calendar_key + FROM dim_calendar + -- one row per day between calendar service start and end date + -- https://stackoverflow.com/questions/38694040/how-to-generate-date-series-to-occupy-absent-dates-in-google-biqquery/58169269#58169269 + LEFT JOIN UNNEST(GENERATE_DATE_ARRAY(start_date, LEAST(end_date, DATE_ADD(CURRENT_DATE(), INTERVAL 1 YEAR)))) AS dt ) SELECT * diff --git a/warehouse/requirements.txt b/warehouse/requirements.txt index 997bd6296e..fd4f955c7d 100644 --- a/warehouse/requirements.txt +++ b/warehouse/requirements.txt @@ -11,15 +11,15 @@ backoff==2.2.1; python_version >= "3.7" and python_version < "4.0" black==22.3.0; python_full_version >= "3.6.2" bleach==5.0.1; python_version >= "3.7" cachetools==5.2.0; python_version >= "3.7" and python_version < "4.0" and (python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "3.11" or python_full_version >= "3.6.0" and python_version >= "3.7" and python_version < "3.11") -certifi==2022.9.24; python_version >= "3.8" and python_version < "4" and python_full_version >= "3.7.2" +certifi==2022.12.7; python_version >= "3.8" and python_version < "4" and python_full_version >= "3.7.2" cffi==1.15.1; python_full_version >= "3.7.2" and python_version >= "3.8" and implementation_name == "pypy" -charset-normalizer==2.1.1; python_version >= "3.7" and python_version < "4" and python_full_version >= "3.7.2" +charset-normalizer==2.1.1; python_full_version >= "3.7.2" and python_version >= "3.7" and python_version < "4" click-plugins==1.1.1; python_version >= "3.7" click==8.0.0; python_version >= "3.6" cligj==0.7.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version < "4" and python_version >= "3.7" cloudpickle==2.2.0; python_version >= "3.8" colorama==0.4.5; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -comm==0.1.1; python_version >= "3.8" +comm==0.1.2; python_version >= "3.8" commonmark==0.9.1; python_full_version >= "3.6.3" and python_full_version < "4.0.0" dask==2022.12.0; python_version >= "3.8" db-dtypes==1.0.5; python_version >= "3.7" and python_version < "3.11" @@ -39,13 +39,13 @@ gcsfs==2022.11.0; python_version >= "3.7" geoalchemy2==0.11.1; python_version >= "3.7" and python_version < "3.11" geopandas==0.10.2; python_version >= "3.7" google-api-core==2.11.0; python_version >= "3.7" and python_version < "3.11" -google-auth-oauthlib==0.7.1; python_version >= "3.7" and python_version < "3.11" +google-auth-oauthlib==0.8.0; python_version >= "3.7" and python_version < "3.11" google-auth==2.15.0; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "3.11" or python_full_version >= "3.6.0" and python_version >= "3.7" and python_version < "3.11" google-cloud-bigquery-storage==2.16.2; python_version >= "3.7" and python_version < "3.11" google-cloud-bigquery==2.34.4; python_version >= "3.7" and python_version < "3.11" google-cloud-core==2.3.2; python_version >= "3.7" and python_version < "3.11" google-cloud-dataproc==5.0.3; python_version >= "3.7" -google-cloud-storage==2.6.0; python_version >= "3.7" +google-cloud-storage==2.7.0; python_version >= "3.7" google-crc32c==1.5.0; python_version >= "3.7" and python_version < "3.11" google-resumable-media==2.4.0; python_version >= "3.7" and python_version < "3.11" googleapis-common-protos==1.57.0; python_version >= "3.7" and python_version < "3.11" @@ -54,17 +54,17 @@ grpcio==1.51.1; python_version >= "3.7" and python_version < "3.11" hologram==0.0.15; python_full_version >= "3.7.2" and python_version >= "3.7" humanize==4.4.0; python_version >= "3.7" idna==3.4; python_version >= "3.7" and python_full_version >= "3.7.2" and python_version < "4" -ipykernel==6.18.3; python_version >= "3.8" +ipykernel==6.19.2; python_version >= "3.8" ipython==8.7.0; python_version >= "3.8" -ipywidgets==8.0.2; python_version >= "3.7" +ipywidgets==8.0.3; python_version >= "3.7" isodate==0.6.1; python_full_version >= "3.7.2" and python_version >= "3.7" -isort==5.10.1; python_full_version >= "3.6.1" and python_version < "4.0" +isort==5.11.1; python_full_version >= "3.7.0" jedi==0.18.2; python_version >= "3.8" jinja2==3.1.2; python_version >= "3.7" and python_full_version >= "3.7.2" jsonschema==3.2.0; python_full_version >= "3.7.2" jupyter-client==7.4.8; python_version >= "3.8" jupyter-core==5.1.0; python_version >= "3.8" -jupyterlab-widgets==3.0.3; python_version >= "3.7" +jupyterlab-widgets==3.0.4; python_version >= "3.7" leather==0.3.4; python_full_version >= "3.7.2" locket==1.0.0; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.8" logbook==1.5.3; python_full_version >= "3.7.2" and python_version >= "3.7" @@ -80,7 +80,7 @@ nest-asyncio==1.5.6; python_version >= "3.8" networkx==2.8.8; python_version >= "3.8" and python_full_version >= "3.7.2" numpy==1.23.5; python_version >= "3.8" and python_version < "3.10" oauthlib==3.2.2; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "3.11" or python_version >= "3.7" and python_version < "3.11" and python_full_version >= "3.4.0" -packaging==21.3; python_version >= "3.8" and python_full_version >= "3.7.2" and python_version < "3.11" +packaging==21.3; python_version >= "3.8" and python_version < "3.11" and python_full_version >= "3.7.2" pandas-gbq==0.17.9; python_version >= "3.7" and python_version < "3.11" pandas==1.5.2; python_version >= "3.8" and python_version < "3.11" parsedatetime==2.4; python_full_version >= "3.7.2" @@ -90,8 +90,8 @@ pathspec==0.9.0; python_full_version >= "3.7.2" and python_version >= "3.7" pendulum==2.1.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") pexpect==4.8.0; sys_platform != "win32" and python_version >= "3.8" pickleshare==0.7.5; python_version >= "3.8" -platformdirs==2.5.4; python_version >= "3.8" and python_full_version >= "3.6.2" -prompt-toolkit==3.0.33; python_full_version >= "3.6.2" and python_version >= "3.8" +platformdirs==2.6.0; python_version >= "3.8" and python_full_version >= "3.6.2" +prompt-toolkit==3.0.36; python_full_version >= "3.6.2" and python_version >= "3.8" proto-plus==1.22.1; python_version >= "3.7" and python_version < "3.11" protobuf==3.20.3; python_version >= "3.7" and python_version < "3.11" psutil==5.9.4; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.8" @@ -105,10 +105,10 @@ pycparser==2.21; python_full_version >= "3.7.2" pydantic==1.10.2; python_version >= "3.7" pydata-google-auth==1.4.0; python_version >= "3.7" and python_version < "3.11" pygments==2.13.0; python_full_version >= "3.6.3" and python_full_version < "4.0.0" and python_version >= "3.8" -pyparsing==3.0.9; python_version >= "3.7" and python_full_version >= "3.7.2" and python_version < "3.11" +pyparsing==3.0.9; python_version >= "3.7" and python_version < "3.11" and python_full_version >= "3.7.2" pyproj==3.4.0; python_version >= "3.8" pyrsistent==0.19.2; python_version >= "3.7" and python_full_version >= "3.7.2" -python-dateutil==2.8.2; python_version >= "3.7" and python_version < "3.11" and python_full_version >= "3.7.2" and (python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "3.11" or python_version >= "3.8" and python_version < "3.11" and python_full_version >= "3.3.0") and (python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.8") +python-dateutil==2.8.2; python_version >= "3.8" and python_version < "3.11" and python_full_version >= "3.7.2" and (python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.8") python-slugify==6.1.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") pytimeparse==1.1.8; python_full_version >= "3.7.2" pytz==2022.6; python_version >= "3.8" and python_version < "3.11" and python_full_version >= "3.7.2" @@ -123,7 +123,7 @@ rich==12.6.0; python_full_version >= "3.6.3" and python_full_version < "4.0.0" rsa==4.9; python_version >= "3.6" and python_version < "4" and (python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "3.11" or python_full_version >= "3.6.0" and python_version >= "3.7" and python_version < "3.11") sentry-sdk==1.11.1 shapely==1.8.5.post1; python_version >= "3.6" -six==1.16.0; python_full_version >= "3.7.2" and python_version >= "3.7" and python_version < "3.11" and (python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "3.11" or python_version >= "3.8" and python_version < "3.11" and python_full_version >= "3.3.0") and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.7") and (python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.8") +six==1.16.0; python_full_version >= "3.7.2" and python_version >= "3.8" and python_version < "3.11" and (python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.7") and (python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.8") sqlalchemy-bigquery==1.5.0; python_version >= "3.7" and python_version < "3.11" sqlalchemy==1.3.24; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") sqlparse==0.4.3; python_version >= "3.7" and python_full_version >= "3.7.2" @@ -134,12 +134,12 @@ tomli==2.0.1; python_version < "3.11" and python_full_version >= "3.6.2" and pyt toolz==0.12.0; python_version >= "3.8" tornado==6.2; python_version >= "3.8" tqdm==4.64.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0") -traitlets==5.6.0; python_version >= "3.8" +traitlets==5.7.1; python_version >= "3.8" typer==0.4.2; python_version >= "3.6" -typing-extensions==4.4.0; python_version >= "3.7" and python_full_version >= "3.7.2" and python_version < "3.10" +typing-extensions==4.4.0; python_version < "3.10" and python_full_version >= "3.7.2" and python_version >= "3.7" urllib3==1.26.13; python_full_version >= "3.7.2" and python_version >= "3.7" and python_version < "4" wcwidth==0.2.5; python_full_version >= "3.6.2" and python_version >= "3.8" webencodings==0.5.1; python_version >= "3.7" werkzeug==2.2.2; python_version >= "3.7" and python_full_version >= "3.7.2" -widgetsnbextension==4.0.3; python_version >= "3.7" +widgetsnbextension==4.0.4; python_version >= "3.7" yarl==1.8.2; python_version >= "3.7"