From a3a22055d8d5d3e6cd3ddd09aa89d25b1c0449ec Mon Sep 17 00:00:00 2001 From: jakeross Date: Wed, 3 Sep 2025 08:51:47 -0600 Subject: [PATCH 01/46] feat: created transfer branch for cloud build --- .github/workflows/tests.yml | 2 +- transfers/link_ids_transfer.py | 1 + transfers/transfer.py | 4 +++- transfers/well_transfer.py | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5efd87168..34a7c677a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,7 +5,7 @@ name: Tests on: pull_request: - branches: [ "main",'pre-production'] + branches: [ "main",'pre-production', 'transfer'] permissions: contents: read diff --git a/transfers/link_ids_transfer.py b/transfers/link_ids_transfer.py index 3e8810a86..54d0abd20 100644 --- a/transfers/link_ids_transfer.py +++ b/transfers/link_ids_transfer.py @@ -63,6 +63,7 @@ def transfer_link_ids_welldata(session): log(row, f"{klass} id does not match regex {regex}, id={aid}") continue + # TODO: add guards for null values link_id = ThingIdLink() link_id.thing = thing link_id.relation = klass diff --git a/transfers/transfer.py b/transfers/transfer.py index f6943b883..79febe075 100644 --- a/transfers/transfer.py +++ b/transfers/transfer.py @@ -13,8 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # =============================================================================== -from sqlalchemy.orm import Session +from dotenv import load_dotenv +load_dotenv() +from sqlalchemy.orm import Session from core.initializers import init_lexicon from db import Base from db.engine import session_ctx diff --git a/transfers/well_transfer.py b/transfers/well_transfer.py index a1ad901a4..d688c4098 100644 --- a/transfers/well_transfer.py +++ b/transfers/well_transfer.py @@ -68,6 +68,7 @@ def transfer_wells(session, limit=None): # print(location_row) session.add(location) + # TODO: add guards for null values well = add_thing( session, { From 1e4665be688917d102d3c26d84324c0a2e545a22 Mon Sep 17 00:00:00 2001 From: jirhiker Date: Wed, 3 Sep 2025 14:52:05 +0000 Subject: [PATCH 02/46] Formatting changes --- transfers/transfer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/transfers/transfer.py b/transfers/transfer.py index 79febe075..fc6ec57a5 100644 --- a/transfers/transfer.py +++ b/transfers/transfer.py @@ -14,6 +14,7 @@ # limitations under the License. # =============================================================================== from dotenv import load_dotenv + load_dotenv() from sqlalchemy.orm import Session From d9b0488536b69cc41d5713f030aef01c8c4b881e Mon Sep 17 00:00:00 2001 From: jakeross Date: Wed, 3 Sep 2025 11:39:00 -0600 Subject: [PATCH 03/46] feat: update asset transfer logic to handle filenames correctly --- transfers/asset_transfer.py | 13 +++++++++---- transfers/transfer.py | 2 +- transfers/util.py | 3 +++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/transfers/asset_transfer.py b/transfers/asset_transfer.py index 804964505..a74ce6450 100644 --- a/transfers/asset_transfer.py +++ b/transfers/asset_transfer.py @@ -41,16 +41,21 @@ def transfer_assets(session: Session) -> None: bucket = get_storage_bucket(client) print(f"Using bucket {bucket.name}") + # for name in ['AR0001']: # for testing for thing in get_valid_things(session): + name = thing.name # find images in temp bucket - print(f"Processing PointID: {thing.name}") - blobs = bucket.list_blobs(prefix=f"nma-photos/{thing.name}") + print(f"Processing PointID: {name}") + blobs = bucket.list_blobs(prefix=f"nma-photos/{name}") # move blobs from temp to assets bucket for srcblob in blobs: f = srcblob.download_as_bytes() - ff = UploadFile(file=io.BytesIO(f), filename=srcblob.name, size=len(f)) + head, filename = srcblob.name.split("/") + + ff = UploadFile(file=io.BytesIO(f), filename=filename, size=len(f)) + uri, blob_name = gcs_upload(ff, bucket) - add_asset(session, ff, srcblob.name, thing.id, uri, blob_name) + add_asset(session, ff, filename, thing.id, uri, blob_name) def transfer_assets_testing(session: Session) -> None: diff --git a/transfers/transfer.py b/transfers/transfer.py index fc6ec57a5..d59d01559 100644 --- a/transfers/transfer.py +++ b/transfers/transfer.py @@ -61,7 +61,7 @@ def main_transfer(): cleanup_wells_flag = False - limit = 100 + limit = 2000 with session_ctx() as sess: if init: diff --git a/transfers/util.py b/transfers/util.py index 953e448f8..f23a1f7a0 100644 --- a/transfers/util.py +++ b/transfers/util.py @@ -35,6 +35,9 @@ def read_csv(name: str) -> pd.DataFrame: def read_csv(name: str) -> pd.DataFrame: + # TODO: grab csv from storage bucket instead of local directory + + p = Path(".") / "data" / name return pd.read_csv(p) From 0689d773f6176a42e165a7d2dbbafd2d722697e0 Mon Sep 17 00:00:00 2001 From: jirhiker Date: Wed, 3 Sep 2025 17:39:17 +0000 Subject: [PATCH 04/46] Formatting changes --- transfers/util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/transfers/util.py b/transfers/util.py index f23a1f7a0..ae62a0303 100644 --- a/transfers/util.py +++ b/transfers/util.py @@ -37,7 +37,6 @@ def read_csv(name: str) -> pd.DataFrame: def read_csv(name: str) -> pd.DataFrame: # TODO: grab csv from storage bucket instead of local directory - p = Path(".") / "data" / name return pd.read_csv(p) From 0b6e98c93b9c2204af50e90a4c31416bbcce031e Mon Sep 17 00:00:00 2001 From: jakeross Date: Wed, 3 Sep 2025 16:42:31 -0600 Subject: [PATCH 05/46] feat: update contact transfer logic to extract roles from OwnerComment. added todos --- transfers/contact_transfer.py | 137 +++++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 4 deletions(-) diff --git a/transfers/contact_transfer.py b/transfers/contact_transfer.py index 4df88ec68..029c08e6f 100644 --- a/transfers/contact_transfer.py +++ b/transfers/contact_transfer.py @@ -29,7 +29,7 @@ def extract_owner_role(comment): # if "Director" in comment: # return "Director" - return "Primary" + return "Owner" def transfer_contacts(session): @@ -45,8 +45,8 @@ def transfer_contacts(session): continue # TODO: extract role from OwnerComment - # role = extract_owner_role(row.OwnerComment) - role = "Primary" + role = extract_owner_role(row.OwnerComment) + # TODO: put in guards for null values # name OR organization must be defined, otherwise skip @@ -59,9 +59,12 @@ def transfer_contacts(session): contact1 = Contact( name=f"{row.FirstName} {row.LastName}", role=role, + # TODO: needs to be implemented + # priority=1, organization=row.Company, # assumes organization applies to both contacts nma_pk_owners=row.OwnerKey, ) + assoc = ThingContactAssociation() assoc.thing = thing assoc.contact = contact1 @@ -109,7 +112,11 @@ def transfer_contacts(session): print(f"Transferring second contact for PointID {row.PointID}") contact2 = Contact( name=f"{row.SecondFirstName} {row.SecondLastName}", - role="Secondary", + role="Secondary", #TODO: role needs to be extracted from somewhere + + # TODO: needs to be implemented + # priority=2, + organization=row.Company, # Assumes organization applies to both contacts nma_pk_owners=row.OwnerKey, ) @@ -131,4 +138,126 @@ def transfer_contacts(session): session.commit() + + # ============= EOF ============================================= +# # =============================================================================== +# # Copyright 2025 ross +# # +# # 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. +# # =============================================================================== +# import numpy as np +# import pandas as pd +# from transfers.util import read_csv, filter_to_valid_point_ids +# from db import Thing, Contact, ThingContactAssociation, Email, Phone, Address +# +# +# def extract_owner_role(comment): +# # if comment is None: +# # return "Owner" +# # if "Owner" in comment: +# # return "Owner" +# # if "Manager" in comment: +# # return "Manager" +# # if "Director" in comment: +# # return "Director" +# +# return "Owner" +# +# +# def transfer_owners(session): +# +# odf = read_csv("ownersdata.csv") +# odf = odf.replace(pd.NA, None) +# odf = odf.replace({np.nan: None}) +# odf = filter_to_valid_point_ids(session, odf) +# for i, row in odf.iterrows(): +# thing = session.query(Thing).where(Thing.name == row.PointID).first() +# if thing is None: +# print(f"Thing with PointID {row.PointID} not foaund. Skipping owner.") +# continue +# +# # TODO: extract role from OwnerComment +# role = extract_owner_role(row.OwnerComment) +# +# # TODO: put in guards for null values +# contact1 = Contact( +# name=f"{row.FirstName} {row.LastName}", +# role=role, +# # TODO: needs to be implemented +# # contact_priority=contact_priority +# # organization_name=company +# ) +# assoc = ThingContactAssociation() +# assoc.thing = thing +# assoc.contact = contact1 +# session.add(assoc) +# session.add(contact1) +# +# if row.Email: +# contact1.emails.append(Email(email=row.Email, email_type="Primary")) +# if row.Phone: +# contact1.phones.append(Phone(phone_number=row.Phone, phone_type="Primary")) +# if row.CellPhone: +# contact1.phones.append( +# Phone(phone_number=row.CellPhone, phone_type="Mobile") +# ) +# +# if row.MailingAddress: +# contact1.addresses.append( +# Address( +# address_line_1=row.MailingAddress, +# city=row.MailCity, +# state=row.MailState, +# postal_code=row.MailZipCode, +# address_type="Mailing", +# ) +# ) +# +# contact1.addresses.append( +# Address( +# address_line_1=row.PhysicalAddress, +# city=row.PhysicalCity, +# state=row.PhysicalState, +# postal_code=row.PhysicalZipCode, +# address_type="Physical", +# ) +# ) +# +# # TODO: put in guards for null values +# contact2 = Contact( +# name=f"{row.SecondFirstName} {row.SecondLastName}", +# role="Secondary" # TODO: role needs to be extracted from somewhere +# # TODO: needs to be implemented +# # organization_name=company +# # contact_priority=contact_priority +# ) +# if row.SecondCtctEmail: +# contact2.emails.append( +# Email(email=row.SecondCtctEmail, email_type="Primary") +# ) +# if row.SecondCtctPhone: +# contact2.phones.append( +# Phone(phone_number=row.SecondCtctPhone, phone_type="Primary") +# ) +# +# assoc = ThingContactAssociation() +# assoc.thing = thing +# assoc.contact = contact2 +# session.add(assoc) +# session.add(contact2) +# +# session.commit() +# +# +# # ============= EOF ============================================= From 3cdd19b3ce47d2ad8b82512d70df35c644b4ffd8 Mon Sep 17 00:00:00 2001 From: jirhiker Date: Wed, 3 Sep 2025 22:42:49 +0000 Subject: [PATCH 06/46] Formatting changes --- transfers/contact_transfer.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/transfers/contact_transfer.py b/transfers/contact_transfer.py index 029c08e6f..6aa750a61 100644 --- a/transfers/contact_transfer.py +++ b/transfers/contact_transfer.py @@ -47,7 +47,6 @@ def transfer_contacts(session): # TODO: extract role from OwnerComment role = extract_owner_role(row.OwnerComment) - # TODO: put in guards for null values # name OR organization must be defined, otherwise skip if not (row.FirstName or row.LastName) and not row.Company: @@ -112,11 +111,9 @@ def transfer_contacts(session): print(f"Transferring second contact for PointID {row.PointID}") contact2 = Contact( name=f"{row.SecondFirstName} {row.SecondLastName}", - role="Secondary", #TODO: role needs to be extracted from somewhere - + role="Secondary", # TODO: role needs to be extracted from somewhere # TODO: needs to be implemented # priority=2, - organization=row.Company, # Assumes organization applies to both contacts nma_pk_owners=row.OwnerKey, ) @@ -138,8 +135,6 @@ def transfer_contacts(session): session.commit() - - # ============= EOF ============================================= # # =============================================================================== # # Copyright 2025 ross From 197c061dbf9f269cc9d6f261421a9b0baa60830c Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 01:22:33 -0600 Subject: [PATCH 07/46] feat: update contact and group transfer logic to use new CSV filenames and improve error handling --- transfers/contact_transfer.py | 184 ++++++++++++++++-------------- transfers/group_transfer.py | 2 +- transfers/link_ids_transfer.py | 4 +- transfers/thing_transfer.py | 4 +- transfers/transfer.py | 55 +++++---- transfers/util.py | 13 ++- transfers/waterlevels_transfer.py | 2 +- transfers/well_transfer.py | 37 +++--- 8 files changed, 166 insertions(+), 135 deletions(-) diff --git a/transfers/contact_transfer.py b/transfers/contact_transfer.py index 6aa750a61..0114fb2ce 100644 --- a/transfers/contact_transfer.py +++ b/transfers/contact_transfer.py @@ -34,106 +34,124 @@ def extract_owner_role(comment): def transfer_contacts(session): - odf = read_csv("ownersdata.csv") + odf = read_csv("OwnersData") + odf = odf.drop(['OBJECTID', 'GlobalID'], axis=1) + ldf = read_csv("OwnerLink") + ldf = ldf.drop(['OBJECTID', 'GlobalID'], axis=1) + locdf = read_csv("Location") + ldf = ldf.join(locdf.set_index("LocationId"), on="LocationId") + + odf = odf.join(ldf.set_index("OwnerKey"), on="OwnerKey") + + odf = odf.replace(pd.NA, None) odf = odf.replace({np.nan: None}) odf = filter_to_valid_point_ids(session, odf) for i, row in odf.iterrows(): - thing = session.query(Thing).where(Thing.name == row.PointID).first() - if thing is None: - print(f"Thing with PointID {row.PointID} not foaund. Skipping owner.") + print(f"Transferring contact for PointID {i} {row.PointID}") + try: + _iterate(session, row) + session.commit() + except Exception as e: + # TODO: log exception + print(f"Error iterating row {i}: {e}") + session.rollback() continue - # TODO: extract role from OwnerComment - role = extract_owner_role(row.OwnerComment) - # TODO: put in guards for null values - # name OR organization must be defined, otherwise skip - if not (row.FirstName or row.LastName) and not row.Company: - print( - f"Skipping first contact for PointID {row.PointID} due to missing name and organization." - ) - else: - print(f"Transferring first contact for PointID {row.PointID}") - contact1 = Contact( - name=f"{row.FirstName} {row.LastName}", - role=role, - # TODO: needs to be implemented - # priority=1, - organization=row.Company, # assumes organization applies to both contacts - nma_pk_owners=row.OwnerKey, - ) +def _iterate(session, row): + thing = session.query(Thing).where(Thing.name == row.PointID).first() + if thing is None: + print(f"Thing with PointID {row.PointID} not foaund. Skipping owner.") + return - assoc = ThingContactAssociation() - assoc.thing = thing - assoc.contact = contact1 - session.add(assoc) - session.add(contact1) + # TODO: extract role from OwnerComment + role = extract_owner_role(row.OwnerComment) - if row.Email: - contact1.emails.append(Email(email=row.Email, email_type="Primary")) - if row.Phone: - contact1.phones.append( - Phone(phone_number=row.Phone, phone_type="Primary") - ) - if row.CellPhone: - contact1.phones.append( - Phone(phone_number=row.CellPhone, phone_type="Mobile") - ) + # TODO: put in guards for null values + # name OR organization must be defined, otherwise skip + if not (row.FirstName or row.LastName) and not row.Company: + print( + f"Skipping first contact for PointID {row.PointID} due to missing name and organization." + ) + else: + print(f"Transferring first contact for PointID {row.PointID}") + contact1 = Contact( + name=f"{row.FirstName} {row.LastName}", + role=role, + # TODO: needs to be implemented + # priority=1, + organization=row.Company, # assumes organization applies to both contacts + nma_pk_owners=row.OwnerKey, + ) - if row.MailingAddress: - contact1.addresses.append( - Address( - address_line_1=row.MailingAddress, - city=row.MailCity, - state=row.MailState, - postal_code=row.MailZipCode, - address_type="Mailing", - ) - ) + assoc = ThingContactAssociation() + assoc.thing = thing + assoc.contact = contact1 + session.add(assoc) + session.add(contact1) - contact1.addresses.append( - Address( - address_line_1=row.PhysicalAddress, - city=row.PhysicalCity, - state=row.PhysicalState, - postal_code=row.PhysicalZipCode, - address_type="Physical", - ) - ) - - # TODO: put in guards for null values - if not (row.SecondFirstName or row.SecondLastName) and not row.Company: - print( - f"Skipping second contact for PointID {row.PointID} due to missing name and organization." + if row.Email: + contact1.emails.append(Email(email=row.Email, email_type="Primary")) + if row.Phone: + contact1.phones.append( + Phone(phone_number=row.Phone, phone_type="Primary") ) - else: - print(f"Transferring second contact for PointID {row.PointID}") - contact2 = Contact( - name=f"{row.SecondFirstName} {row.SecondLastName}", - role="Secondary", # TODO: role needs to be extracted from somewhere - # TODO: needs to be implemented - # priority=2, - organization=row.Company, # Assumes organization applies to both contacts - nma_pk_owners=row.OwnerKey, + if row.CellPhone: + contact1.phones.append( + Phone(phone_number=row.CellPhone, phone_type="Mobile") ) - if row.SecondCtctEmail: - contact2.emails.append( - Email(email=row.SecondCtctEmail, email_type="Primary") - ) - if row.SecondCtctPhone: - contact2.phones.append( - Phone(phone_number=row.SecondCtctPhone, phone_type="Primary") + + if row.MailingAddress: + contact1.addresses.append( + Address( + address_line_1=row.MailingAddress, + city=row.MailCity, + state=row.MailState, + postal_code=row.MailZipCode, + address_type="Mailing", ) + ) - assoc = ThingContactAssociation() - assoc.thing = thing - assoc.contact = contact2 - session.add(assoc) - session.add(contact2) + contact1.addresses.append( + Address( + address_line_1=row.PhysicalAddress, + city=row.PhysicalCity, + state=row.PhysicalState, + postal_code=row.PhysicalZipCode, + address_type="Physical", + ) + ) - session.commit() + # TODO: put in guards for null values + if not (row.SecondFirstName or row.SecondLastName) and not row.Company: + print( + f"Skipping second contact for PointID {row.PointID} due to missing name and organization." + ) + else: + print(f"Transferring second contact for PointID {row.PointID}") + contact2 = Contact( + name=f"{row.SecondFirstName} {row.SecondLastName}", + role="Secondary", # TODO: role needs to be extracted from somewhere + # TODO: needs to be implemented + # priority=2, + organization=row.Company, # Assumes organization applies to both contacts + nma_pk_owners=row.OwnerKey, + ) + if row.SecondCtctEmail: + contact2.emails.append( + Email(email=row.SecondCtctEmail, email_type="Primary") + ) + if row.SecondCtctPhone: + contact2.phones.append( + Phone(phone_number=row.SecondCtctPhone, phone_type="Primary") + ) + assoc = ThingContactAssociation() + assoc.thing = thing + assoc.contact = contact2 + session.add(assoc) + session.add(contact2) # ============= EOF ============================================= # # =============================================================================== diff --git a/transfers/group_transfer.py b/transfers/group_transfer.py index d49527e1e..d02612a2a 100644 --- a/transfers/group_transfer.py +++ b/transfers/group_transfer.py @@ -24,7 +24,7 @@ def transfer_groups( session: Session, ) -> None: - wdf = read_csv("projects.csv") + wdf = read_csv("Projects") for i, row in enumerate(wdf.itertuples()): sql = select(Group).where(Group.name == row.Project) diff --git a/transfers/link_ids_transfer.py b/transfers/link_ids_transfer.py index 54d0abd20..7c6a3585a 100644 --- a/transfers/link_ids_transfer.py +++ b/transfers/link_ids_transfer.py @@ -26,7 +26,7 @@ def transfer_link_ids_welldata(session): - ldf = read_csv("welldata.csv") + ldf = read_csv("WellData") ldf = filter_to_valid_point_ids(session, ldf) @@ -140,7 +140,7 @@ def add_link_plss(session, row, thing): def transfer_link_ids(session, site_type="GW"): - ldf = read_csv("location2.csv") + ldf = read_csv("Location") ldf = ldf[ldf["SiteType"] == site_type] ldf = ldf[ldf["Easting"].notna() & ldf["Northing"].notna()] # ldf = ldf[ldf["AlternateSiteID"].notna()] diff --git a/transfers/thing_transfer.py b/transfers/thing_transfer.py index 6c85312be..5dc84892d 100644 --- a/transfers/thing_transfer.py +++ b/transfers/thing_transfer.py @@ -25,7 +25,7 @@ def transfer_thing(session: Session, site_type: str, make_payload, limit=None) -> None: - ldf = read_csv("location.csv") + ldf = read_csv("Location") ldf = ldf[ldf["SiteType"] == site_type] ldf = ldf[ldf["Easting"].notna() & ldf["Northing"].notna()] n = len(ldf) @@ -35,7 +35,7 @@ def transfer_thing(session: Session, site_type: str, make_payload, limit=None) - print(f"Reached limit of {limit} rows. Stopping migration.") break - if i and not i % 100: + if i and not i % 25: print( f"Processing row {i} of {n}. {row.PointID}, avg rows per second: {i / (time.time() - start_time):.2f}" ) diff --git a/transfers/transfer.py b/transfers/transfer.py index 2899c4ea5..01f547f6a 100644 --- a/transfers/transfer.py +++ b/transfers/transfer.py @@ -45,6 +45,11 @@ def erase_and_initalize(session: Session) -> None: init_sensor(session) +def message(msg, pad=10): + pad = "*" * pad + print(f'{pad} {msg} {pad}\n') + + def main_transfer(): init = True @@ -61,52 +66,52 @@ def main_transfer(): cleanup_wells_flag = False - limit = 2000 + limit = 1000 with session_ctx() as sess: if init: erase_and_initalize(sess) if init or transfer_well_flag: - print("\n", "*" * 10, "TRANSFERRING WELLS", "*" * 10) + message("TRANSFERRING WELLS") transfer_wells(sess, limit) transfer_wellscreens(sess) - - if init or transfer_spring_flag: - print("\n", "*" * 10, "TRANSFERRING SPRINGS", "*" * 10) - transfer_springs(sess, limit) - - if init or transfer_perennial_stream_flag: - print("\n", "*" * 10, "TRANSFERRING PERENNIAL STREAMS", "*" * 10) - transfer_perennial_stream(sess, limit) - - if init or transfer_ephemeral_stream_flag: - print("\n", "*" * 10, "TRANSFERRING EPHEMERAL STREAMS", "*" * 10) - transfer_ephemeral_stream(sess, limit) - - if init or transfer_met_flag: - print("\n", "*" * 10, "TRANSFERRING METEOROLOGICAL", "*" * 10) - transfer_met(sess, limit) + # + # if init or transfer_spring_flag: + # message("TRANSFERRING SPRINGS") + # transfer_springs(sess, limit) + # + # if init or transfer_perennial_stream_flag: + # message("TRANSFERRING PERENNIAL STREAMS") + # transfer_perennial_stream(sess, limit) + # + # if init or transfer_ephemeral_stream_flag: + # message("TRANSFERRING EPHEMERAL STREAMS") + # transfer_ephemeral_stream(sess, limit) + # + # if init or transfer_met_flag: + # message("TRANSFERRING METEOROLOGICAL") + # transfer_met(sess, limit) if init or transfer_contacts_flag: - print("\n", "*" * 10, "TRANSFERRING CONTACTS", "*" * 10) + message("TRANSFERRING CONTACTS") transfer_contacts(sess) if init or transfer_waterlevels_flag: - print("\n", "*" * 10, "TRANSFERRING WATER LEVELS", "*" * 10) + message("TRANSFERRING WATER LEVELS") transfer_water_levels(sess) if init or transfer_link_ids_flag: - print("\n", "*" * 10, "TRANSFERRING LINK IDS", "*" * 10) + message("TRANSFERRING LINK IDS") transfer_link_ids(sess) transfer_link_ids_welldata(sess) - if init or transfer_assets_flag: - print("\n", "*" * 10, "TRANSFERRING ASSETS", "*" * 10) - transfer_assets_testing(sess) + # if init or transfer_assets_flag: + # message("TRANSFERRING ASSETS") + # transfer_assets_testing(sess) if init or transfer_groups_flag: - print("\n", "*" * 10, "TRANSFERRING GROUPS", "*" * 10) + message("TRANSFERRING GROUPS") transfer_groups(sess) # if init or cleanup_wells_flag: diff --git a/transfers/util.py b/transfers/util.py index fa7c5d24b..fc9a3c44a 100644 --- a/transfers/util.py +++ b/transfers/util.py @@ -14,6 +14,7 @@ # limitations under the License. # =============================================================================== import re +import io from pathlib import Path import httpx @@ -25,15 +26,16 @@ import pandas as pd from db import Thing, Location +from services.gcs_helper import get_storage_bucket TRANSFORMERS = {} def read_csv(name: str) -> pd.DataFrame: - # TODO: grab csv from storage bucket instead of local directory - - p = Path(".") / "transfers" / "data" / name - return pd.read_csv(p) + bucket = get_storage_bucket() + blob = bucket.blob(f"nma_csv/{name}.csv") + data = blob.download_as_bytes() + return pd.read_csv(io.BytesIO(data)) def transform_srid(geometry, source_srid, target_srid): @@ -207,10 +209,11 @@ def make_location(row: pd.Series) -> Location: # TODO: determine correct created_at value # created_at = row.DateCreated + name = row.PointID location = Location( # nma_pk_location=row.LocationId, - name=row.PointID, + name=name, point=transformed_point.wkt, release_status="public" if row.PublicRelease else "private", # elevation_accuracy=row.AltitudeAccuracy, diff --git a/transfers/waterlevels_transfer.py b/transfers/waterlevels_transfer.py index 6ecefc621..aeccd59c9 100644 --- a/transfers/waterlevels_transfer.py +++ b/transfers/waterlevels_transfer.py @@ -24,7 +24,7 @@ def transfer_water_levels(session): - wd = read_csv("water_levels.csv") + wd = read_csv("WaterLevels") wd = filter_to_valid_point_ids(session, wd) gwd = wd.groupby(["PointID"]) diff --git a/transfers/well_transfer.py b/transfers/well_transfer.py index d688c4098..1c24c44ee 100644 --- a/transfers/well_transfer.py +++ b/transfers/well_transfer.py @@ -38,10 +38,10 @@ def transfer_wells(session, limit=None): - wdf = read_csv("welldata.csv") - ldf = read_csv("location.csv") - - wdf = wdf.join(ldf.set_index("PointID"), on="PointID") + wdf = read_csv("WellData") + ldf = read_csv("Location") + ldf = ldf.drop(['PointID', 'SSMA_TimeStamp'], axis=1) + wdf = wdf.join(ldf.set_index("LocationId"), on="LocationId") wdf = wdf[wdf["SiteType"] == "GW"] wdf = wdf[wdf["Easting"].notna() & wdf["Northing"].notna()] @@ -55,7 +55,8 @@ def transfer_wells(session, limit=None): if i and not i % 25: print( - f"Processing row {i} of {n}. {row.PointID}, avg rows per second: {i / (time.time() - start_time):.2f}" + f"Processing row {i} of {n}. {row.PointID}, avg rows per second:" + f" {i / (time.time() - start_time):.2f}" ) session.commit() @@ -63,6 +64,7 @@ def transfer_wells(session, limit=None): location = make_location(row) except Exception as e: print(f"Error making location for row {i}: {e}") + print(row) break # print(location_row) @@ -86,17 +88,20 @@ def transfer_wells(session, limit=None): }, thing_type="water well", ) - wt = row.Meaning - if wt not in ADDED: - add_lexicon_term( - session, - wt, - "Current use of the well, aka well purpose", - [{"name": "current_use", "desciption": "Current use of the well"}], - ) - ADDED.append(wt) - well.well_type = wt + # TODO: use current use LUT to get well type + + # wt = row.Meaning + # if wt not in ADDED: + # add_lexicon_term( + # session, + # wt, + # "Current use of the well, aka well purpose", + # [{"name": "current_use", "desciption": "Current use of the well"}], + # ) + # ADDED.append(wt) + # + # well.well_type = wt assoc = LocationThingAssociation() @@ -106,7 +111,7 @@ def transfer_wells(session, limit=None): def transfer_wellscreens(session, limit=None): - wdf = read_csv("wellscreens.csv") + wdf = read_csv("WellScreens") wdf = wdf.replace(pd.NA, None) wdf = wdf.replace({np.nan: None}) From 3907d7a628ee83b0acd8a61a9b93bae65f3ddbd0 Mon Sep 17 00:00:00 2001 From: jirhiker Date: Thu, 4 Sep 2025 07:22:50 +0000 Subject: [PATCH 08/46] Formatting changes --- transfers/contact_transfer.py | 10 ++++------ transfers/transfer.py | 2 +- transfers/well_transfer.py | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/transfers/contact_transfer.py b/transfers/contact_transfer.py index 0114fb2ce..e05eddd7a 100644 --- a/transfers/contact_transfer.py +++ b/transfers/contact_transfer.py @@ -35,15 +35,14 @@ def extract_owner_role(comment): def transfer_contacts(session): odf = read_csv("OwnersData") - odf = odf.drop(['OBJECTID', 'GlobalID'], axis=1) + odf = odf.drop(["OBJECTID", "GlobalID"], axis=1) ldf = read_csv("OwnerLink") - ldf = ldf.drop(['OBJECTID', 'GlobalID'], axis=1) + ldf = ldf.drop(["OBJECTID", "GlobalID"], axis=1) locdf = read_csv("Location") ldf = ldf.join(locdf.set_index("LocationId"), on="LocationId") odf = odf.join(ldf.set_index("OwnerKey"), on="OwnerKey") - odf = odf.replace(pd.NA, None) odf = odf.replace({np.nan: None}) odf = filter_to_valid_point_ids(session, odf) @@ -94,9 +93,7 @@ def _iterate(session, row): if row.Email: contact1.emails.append(Email(email=row.Email, email_type="Primary")) if row.Phone: - contact1.phones.append( - Phone(phone_number=row.Phone, phone_type="Primary") - ) + contact1.phones.append(Phone(phone_number=row.Phone, phone_type="Primary")) if row.CellPhone: contact1.phones.append( Phone(phone_number=row.CellPhone, phone_type="Mobile") @@ -153,6 +150,7 @@ def _iterate(session, row): session.add(assoc) session.add(contact2) + # ============= EOF ============================================= # # =============================================================================== # # Copyright 2025 ross diff --git a/transfers/transfer.py b/transfers/transfer.py index 01f547f6a..8f9f9319d 100644 --- a/transfers/transfer.py +++ b/transfers/transfer.py @@ -47,7 +47,7 @@ def erase_and_initalize(session: Session) -> None: def message(msg, pad=10): pad = "*" * pad - print(f'{pad} {msg} {pad}\n') + print(f"{pad} {msg} {pad}\n") def main_transfer(): diff --git a/transfers/well_transfer.py b/transfers/well_transfer.py index 1c24c44ee..ec1f9bc52 100644 --- a/transfers/well_transfer.py +++ b/transfers/well_transfer.py @@ -40,7 +40,7 @@ def transfer_wells(session, limit=None): wdf = read_csv("WellData") ldf = read_csv("Location") - ldf = ldf.drop(['PointID', 'SSMA_TimeStamp'], axis=1) + ldf = ldf.drop(["PointID", "SSMA_TimeStamp"], axis=1) wdf = wdf.join(ldf.set_index("LocationId"), on="LocationId") wdf = wdf[wdf["SiteType"] == "GW"] wdf = wdf[wdf["Easting"].notna() & wdf["Northing"].notna()] From ff6c51b442e9dcf04455d1e9d0254361e36c094f Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 02:22:31 -0600 Subject: [PATCH 09/46] feat: add health check endpoint and middleware for improved service monitoring --- core/app.py | 11 +++++++---- main.py | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/core/app.py b/core/app.py index 0c9cda20a..739b36acc 100644 --- a/core/app.py +++ b/core/app.py @@ -19,6 +19,7 @@ from fastapi import FastAPI from sqlalchemy import text +from starlette.responses import JSONResponse from .dependencies import session_dependency from .initializers import init_db, init_lexicon @@ -44,10 +45,12 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: ) -@app.get("/_ah/warmup") -async def warmup(session: session_dependency): - session.execute(text("SELECT 1")) - return HTTPStatus.OK +@app.get("/ping", include_in_schema=False) +async def ping() -> JSONResponse: + """ Health check endpoint to verify the service is running. + """ + return JSONResponse(status_code=HTTPStatus.OK, content={"ping": "pong!"}) + # ============= EOF ============================================= diff --git a/main.py b/main.py index 546999bc4..7a4f3cbcb 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,8 @@ import os import sentry_sdk from dotenv import load_dotenv +from starlette.middleware.base import BaseHTTPMiddleware +from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware load_dotenv() From bdad4d92117f2ea835f70af6cc1d6ae36d132664 Mon Sep 17 00:00:00 2001 From: jirhiker Date: Thu, 4 Sep 2025 08:22:48 +0000 Subject: [PATCH 10/46] Formatting changes --- core/app.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/app.py b/core/app.py index 739b36acc..3c0c3b427 100644 --- a/core/app.py +++ b/core/app.py @@ -47,10 +47,8 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: @app.get("/ping", include_in_schema=False) async def ping() -> JSONResponse: - """ Health check endpoint to verify the service is running. - """ + """Health check endpoint to verify the service is running.""" return JSONResponse(status_code=HTTPStatus.OK, content={"ping": "pong!"}) - # ============= EOF ============================================= From 6398a0265cabbd19926469994d0ae3797ca56d1e Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 02:40:23 -0600 Subject: [PATCH 11/46] feat: add FastAPI entrypoint with trigger endpoint for transfer service --- transfers/entrypoint.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 transfers/entrypoint.py diff --git a/transfers/entrypoint.py b/transfers/entrypoint.py new file mode 100644 index 000000000..2b19178a0 --- /dev/null +++ b/transfers/entrypoint.py @@ -0,0 +1,27 @@ +# =============================================================================== +# Copyright 2025 ross +# +# 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. +# =============================================================================== +from fastapi import FastAPI + +from transfers.transfer import main_transfer + +app = FastAPI(title='Transfer Service') + +@app.get('/trigger') +async def trigger(): + main_transfer() + + return +# ============= EOF ============================================= From 5cc8fae31944c3e9ba95ee2bf6050ec626f9a56c Mon Sep 17 00:00:00 2001 From: jirhiker Date: Thu, 4 Sep 2025 08:40:41 +0000 Subject: [PATCH 12/46] Formatting changes --- transfers/entrypoint.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/transfers/entrypoint.py b/transfers/entrypoint.py index 2b19178a0..ec2220f3e 100644 --- a/transfers/entrypoint.py +++ b/transfers/entrypoint.py @@ -17,11 +17,14 @@ from transfers.transfer import main_transfer -app = FastAPI(title='Transfer Service') +app = FastAPI(title="Transfer Service") -@app.get('/trigger') + +@app.get("/trigger") async def trigger(): main_transfer() return + + # ============= EOF ============================================= From fda9d27e0ce4a54709b531f175f02a1c94d72b87 Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 10:48:18 -0600 Subject: [PATCH 13/46] feat: add main entry point for FastAPI application with configurable port --- transfers/entrypoint.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/transfers/entrypoint.py b/transfers/entrypoint.py index ec2220f3e..082565b45 100644 --- a/transfers/entrypoint.py +++ b/transfers/entrypoint.py @@ -13,6 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. # =============================================================================== +import os + +import uvicorn from fastapi import FastAPI from transfers.transfer import main_transfer @@ -26,5 +29,7 @@ async def trigger(): return - +if __name__ == "__main__": + port = int(os.getenv("PORT", 8080)) + uvicorn.run(app, host="0.0.0.0", port=port) # ============= EOF ============================================= From 9ca5c141ea3fbb55e7f4ca79bbd3a92e266457c0 Mon Sep 17 00:00:00 2001 From: jirhiker Date: Thu, 4 Sep 2025 16:48:35 +0000 Subject: [PATCH 14/46] Formatting changes --- transfers/entrypoint.py | 1 + 1 file changed, 1 insertion(+) diff --git a/transfers/entrypoint.py b/transfers/entrypoint.py index 082565b45..86e779ffc 100644 --- a/transfers/entrypoint.py +++ b/transfers/entrypoint.py @@ -29,6 +29,7 @@ async def trigger(): return + if __name__ == "__main__": port = int(os.getenv("PORT", 8080)) uvicorn.run(app, host="0.0.0.0", port=port) From a4dce0cbec897db31c3056040fc0e1c6618471e7 Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 10:55:27 -0600 Subject: [PATCH 15/46] feat: remove uvicorn entrypoint and add Procfile for Gunicorn deployment --- Procfile | 1 + transfers/entrypoint.py | 6 ------ 2 files changed, 1 insertion(+), 6 deletions(-) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..63150e438 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn -b :8080 entrypoint:app diff --git a/transfers/entrypoint.py b/transfers/entrypoint.py index 86e779ffc..40a8139d1 100644 --- a/transfers/entrypoint.py +++ b/transfers/entrypoint.py @@ -13,9 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # =============================================================================== -import os -import uvicorn from fastapi import FastAPI from transfers.transfer import main_transfer @@ -29,8 +27,4 @@ async def trigger(): return - -if __name__ == "__main__": - port = int(os.getenv("PORT", 8080)) - uvicorn.run(app, host="0.0.0.0", port=port) # ============= EOF ============================================= From 0fdb2272249e39a5f886d457a07d3053e3a66b41 Mon Sep 17 00:00:00 2001 From: jirhiker Date: Thu, 4 Sep 2025 16:56:07 +0000 Subject: [PATCH 16/46] Formatting changes --- transfers/entrypoint.py | 1 + 1 file changed, 1 insertion(+) diff --git a/transfers/entrypoint.py b/transfers/entrypoint.py index 40a8139d1..7c2aed583 100644 --- a/transfers/entrypoint.py +++ b/transfers/entrypoint.py @@ -27,4 +27,5 @@ async def trigger(): return + # ============= EOF ============================================= From 9f515b5a997d9b534030fce80fa3c2f7342d1166 Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 11:00:26 -0600 Subject: [PATCH 17/46] feat: update Procfile to use UvicornWorker for Gunicorn deployment --- Procfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Procfile b/Procfile index 63150e438..8e59db9b4 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: gunicorn -b :8080 entrypoint:app +web: gunicorn -b :8080 entrypoint:app -k uvicorn.workers.UvicornWorker From 007bdc7ef62d67a7a9d6b534a3645fdaaded295f Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 14:32:56 -0600 Subject: [PATCH 18/46] feat: add requirements_transfer.txt for transfer service dependencies --- requirements_transfer.txt | 378 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 378 insertions(+) create mode 100644 requirements_transfer.txt diff --git a/requirements_transfer.txt b/requirements_transfer.txt new file mode 100644 index 000000000..4f264858c --- /dev/null +++ b/requirements_transfer.txt @@ -0,0 +1,378 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml -o requirements.txt +aiofiles==24.1.0 + # via + # nmsamplelocations (pyproject.toml) + # cloud-sql-python-connector +aiohappyeyeballs==2.6.1 + # via + # nmsamplelocations (pyproject.toml) + # aiohttp +aiohttp==3.12.15 + # via + # nmsamplelocations (pyproject.toml) + # cloud-sql-python-connector +aiosignal==1.4.0 + # via + # nmsamplelocations (pyproject.toml) + # aiohttp +aiosqlite==0.21.0 + # via nmsamplelocations (pyproject.toml) +alembic==1.16.4 + # via nmsamplelocations (pyproject.toml) +annotated-types==0.7.0 + # via + # nmsamplelocations (pyproject.toml) + # pydantic +anyio==4.10.0 + # via + # nmsamplelocations (pyproject.toml) + # httpx + # starlette +asgiref==3.9.1 + # via nmsamplelocations (pyproject.toml) +asn1crypto==1.5.1 + # via + # nmsamplelocations (pyproject.toml) + # scramp +asyncpg==0.30.0 + # via nmsamplelocations (pyproject.toml) +attrs==25.3.0 + # via + # nmsamplelocations (pyproject.toml) + # aiohttp +authlib==1.6.1 + # via nmsamplelocations (pyproject.toml) +bcrypt==4.3.0 + # via nmsamplelocations (pyproject.toml) +cachetools==5.5.2 + # via + # nmsamplelocations (pyproject.toml) + # google-auth +certifi==2025.8.3 + # via + # nmsamplelocations (pyproject.toml) + # httpcore + # httpx + # pyproj + # requests + # sentry-sdk +cffi==1.17.1 + # via + # nmsamplelocations (pyproject.toml) + # cryptography +cfgv==3.4.0 + # via pre-commit +charset-normalizer==3.4.3 + # via + # nmsamplelocations (pyproject.toml) + # requests +click==8.2.1 + # via + # nmsamplelocations (pyproject.toml) + # uvicorn +cloud-sql-python-connector==1.18.4 + # via nmsamplelocations (pyproject.toml) +coverage==7.10.2 + # via pytest-cov +cryptography==45.0.6 + # via + # nmsamplelocations (pyproject.toml) + # authlib + # cloud-sql-python-connector +distlib==0.4.0 + # via virtualenv +dnspython==2.7.0 + # via + # nmsamplelocations (pyproject.toml) + # cloud-sql-python-connector + # email-validator +dotenv==0.9.9 + # via nmsamplelocations (pyproject.toml) +ecdsa==0.19.1 + # via python-jose +email-validator==2.2.0 + # via nmsamplelocations (pyproject.toml) +fastapi==0.116.1 + # via + # nmsamplelocations (pyproject.toml) + # fastapi-pagination + # sentry-sdk +fastapi-pagination==0.13.3 + # via nmsamplelocations (pyproject.toml) +filelock==3.18.0 + # via virtualenv +frozenlist==1.7.0 + # via + # nmsamplelocations (pyproject.toml) + # aiohttp + # aiosignal +geoalchemy2==0.18.0 + # via nmsamplelocations (pyproject.toml) +google-api-core==2.25.1 + # via + # nmsamplelocations (pyproject.toml) + # google-cloud-core + # google-cloud-storage +google-auth==2.40.3 + # via + # nmsamplelocations (pyproject.toml) + # cloud-sql-python-connector + # google-api-core + # google-cloud-core + # google-cloud-storage +google-cloud-core==2.4.3 + # via + # nmsamplelocations (pyproject.toml) + # google-cloud-storage +google-cloud-storage==3.2.0 + # via nmsamplelocations (pyproject.toml) +google-crc32c==1.7.1 + # via + # nmsamplelocations (pyproject.toml) + # google-cloud-storage + # google-resumable-media +google-resumable-media==2.7.2 + # via + # nmsamplelocations (pyproject.toml) + # google-cloud-storage +googleapis-common-protos==1.70.0 + # via + # nmsamplelocations (pyproject.toml) + # google-api-core +greenlet==3.2.4 + # via nmsamplelocations (pyproject.toml) +gunicorn==23.0.0 + # via nmsamplelocations (pyproject.toml) +h11==0.16.0 + # via + # nmsamplelocations (pyproject.toml) + # httpcore + # uvicorn +httpcore==1.0.9 + # via + # nmsamplelocations (pyproject.toml) + # httpx +httpx==0.28.1 + # via nmsamplelocations (pyproject.toml) +identify==2.6.12 + # via pre-commit +idna==3.10 + # via + # nmsamplelocations (pyproject.toml) + # anyio + # email-validator + # httpx + # requests + # yarl +iniconfig==2.1.0 + # via + # nmsamplelocations (pyproject.toml) + # pytest +itsdangerous==2.2.0 + # via nmsamplelocations (pyproject.toml) +jinja2==3.1.6 + # via nmsamplelocations (pyproject.toml) +mako==1.3.10 + # via + # nmsamplelocations (pyproject.toml) + # alembic +markupsafe==3.0.2 + # via + # nmsamplelocations (pyproject.toml) + # jinja2 + # mako +multidict==6.6.3 + # via + # nmsamplelocations (pyproject.toml) + # aiohttp + # yarl +nodeenv==1.9.1 + # via pre-commit +numpy==2.3.2 + # via + # nmsamplelocations (pyproject.toml) + # pandas + # pandas-stubs + # shapely +packaging==25.0 + # via + # nmsamplelocations (pyproject.toml) + # geoalchemy2 + # gunicorn + # pytest +pandas==2.3.1 + # via nmsamplelocations (pyproject.toml) +pandas-stubs==2.3.0.250703 + # via nmsamplelocations (pyproject.toml) +pg8000==1.31.4 + # via nmsamplelocations (pyproject.toml) +phonenumbers==9.0.12 + # via nmsamplelocations (pyproject.toml) +pillow==11.3.0 + # via nmsamplelocations (pyproject.toml) +platformdirs==4.3.8 + # via virtualenv +pluggy==1.6.0 + # via + # nmsamplelocations (pyproject.toml) + # pytest + # pytest-cov +pre-commit==4.3.0 + # via nmsamplelocations (pyproject.toml) +propcache==0.3.2 + # via + # nmsamplelocations (pyproject.toml) + # aiohttp + # yarl +proto-plus==1.26.1 + # via + # nmsamplelocations (pyproject.toml) + # google-api-core +protobuf==6.32.0 + # via + # nmsamplelocations (pyproject.toml) + # google-api-core + # googleapis-common-protos + # proto-plus +psycopg2==2.9.10 + # via nmsamplelocations (pyproject.toml) +pyasn1==0.6.1 + # via + # nmsamplelocations (pyproject.toml) + # pyasn1-modules + # python-jose + # rsa +pyasn1-modules==0.4.2 + # via + # nmsamplelocations (pyproject.toml) + # google-auth +pycparser==2.22 + # via + # nmsamplelocations (pyproject.toml) + # cffi +pydantic==2.11.7 + # via + # nmsamplelocations (pyproject.toml) + # fastapi + # fastapi-pagination +pydantic-core==2.33.2 + # via + # nmsamplelocations (pyproject.toml) + # pydantic +pygments==2.19.2 + # via + # nmsamplelocations (pyproject.toml) + # pytest +pyjwt==2.10.1 + # via nmsamplelocations (pyproject.toml) +pyproj==3.7.1 + # via nmsamplelocations (pyproject.toml) +pyshp==2.3.1 + # via nmsamplelocations (pyproject.toml) +pytest==8.4.1 + # via + # nmsamplelocations (pyproject.toml) + # pytest-cov +pytest-cov==6.2.1 + # via nmsamplelocations (pyproject.toml) +python-dateutil==2.9.0.post0 + # via + # nmsamplelocations (pyproject.toml) + # pandas + # pg8000 +python-dotenv==1.1.1 + # via dotenv +python-jose==3.5.0 + # via nmsamplelocations (pyproject.toml) +python-multipart==0.0.20 + # via nmsamplelocations (pyproject.toml) +pytz==2025.2 + # via + # nmsamplelocations (pyproject.toml) + # pandas +pyyaml==6.0.2 + # via pre-commit +requests==2.32.4 + # via + # nmsamplelocations (pyproject.toml) + # cloud-sql-python-connector + # google-api-core + # google-cloud-storage +rsa==4.9.1 + # via + # nmsamplelocations (pyproject.toml) + # google-auth + # python-jose +scramp==1.4.6 + # via + # nmsamplelocations (pyproject.toml) + # pg8000 +sentry-sdk==2.35.0 + # via nmsamplelocations (pyproject.toml) +shapely==2.1.1 + # via nmsamplelocations (pyproject.toml) +six==1.17.0 + # via + # nmsamplelocations (pyproject.toml) + # ecdsa + # python-dateutil +sniffio==1.3.1 + # via + # nmsamplelocations (pyproject.toml) + # anyio +sqlalchemy==2.0.43 + # via + # nmsamplelocations (pyproject.toml) + # alembic + # geoalchemy2 + # sqlalchemy-continuum + # sqlalchemy-searchable + # sqlalchemy-utils +sqlalchemy-continuum==1.4.2 + # via nmsamplelocations (pyproject.toml) +sqlalchemy-searchable==2.1.0 + # via nmsamplelocations (pyproject.toml) +sqlalchemy-utils==0.41.2 + # via + # nmsamplelocations (pyproject.toml) + # sqlalchemy-continuum + # sqlalchemy-searchable +starlette==0.47.2 + # via + # nmsamplelocations (pyproject.toml) + # fastapi +types-pytz==2025.2.0.20250809 + # via pandas-stubs +typing-extensions==4.14.1 + # via + # nmsamplelocations (pyproject.toml) + # aiosqlite + # alembic + # fastapi + # fastapi-pagination + # pydantic + # pydantic-core + # sqlalchemy + # typing-inspection +typing-inspection==0.4.1 + # via + # nmsamplelocations (pyproject.toml) + # pydantic +tzdata==2025.2 + # via + # nmsamplelocations (pyproject.toml) + # pandas +urllib3==2.5.0 + # via + # nmsamplelocations (pyproject.toml) + # requests + # sentry-sdk +uvicorn==0.35.0 + # via nmsamplelocations (pyproject.toml) +virtualenv==20.32.0 + # via pre-commit +yarl==1.20.1 + # via + # nmsamplelocations (pyproject.toml) + # aiohttp From 53e8f6d8fe64153aad594f9d365c6d167bd9765d Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 14:48:37 -0600 Subject: [PATCH 19/46] feat: add requirements.txt for transfer service dependencies --- requirements_transfer.txt => requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename requirements_transfer.txt => requirements.txt (100%) diff --git a/requirements_transfer.txt b/requirements.txt similarity index 100% rename from requirements_transfer.txt rename to requirements.txt From d316cd72921b9fc5c1326472b2dbf2e889f1050e Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 14:54:44 -0600 Subject: [PATCH 20/46] feat: add psycopg-binary dependency to pyproject.toml and update lock files --- pyproject.toml | 1 + requirements.txt | 429 ++++++----------------------------------------- uv.lock | 20 +++ 3 files changed, 72 insertions(+), 378 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 38c275b7e..d56ea3af6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,7 @@ dependencies = [ "propcache==0.3.2", "proto-plus==1.26.1", "protobuf==6.32.0", + "psycopg-binary>=3.2.9", "psycopg2==2.9.10", "pyasn1==0.6.1", "pyasn1-modules==0.4.2", diff --git a/requirements.txt b/requirements.txt index 4f264858c..8f756f98e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,378 +1,51 @@ -# This file was autogenerated by uv via the following command: -# uv pip compile pyproject.toml -o requirements.txt -aiofiles==24.1.0 - # via - # nmsamplelocations (pyproject.toml) - # cloud-sql-python-connector -aiohappyeyeballs==2.6.1 - # via - # nmsamplelocations (pyproject.toml) - # aiohttp -aiohttp==3.12.15 - # via - # nmsamplelocations (pyproject.toml) - # cloud-sql-python-connector -aiosignal==1.4.0 - # via - # nmsamplelocations (pyproject.toml) - # aiohttp -aiosqlite==0.21.0 - # via nmsamplelocations (pyproject.toml) -alembic==1.16.4 - # via nmsamplelocations (pyproject.toml) -annotated-types==0.7.0 - # via - # nmsamplelocations (pyproject.toml) - # pydantic -anyio==4.10.0 - # via - # nmsamplelocations (pyproject.toml) - # httpx - # starlette -asgiref==3.9.1 - # via nmsamplelocations (pyproject.toml) -asn1crypto==1.5.1 - # via - # nmsamplelocations (pyproject.toml) - # scramp -asyncpg==0.30.0 - # via nmsamplelocations (pyproject.toml) -attrs==25.3.0 - # via - # nmsamplelocations (pyproject.toml) - # aiohttp -authlib==1.6.1 - # via nmsamplelocations (pyproject.toml) -bcrypt==4.3.0 - # via nmsamplelocations (pyproject.toml) -cachetools==5.5.2 - # via - # nmsamplelocations (pyproject.toml) - # google-auth -certifi==2025.8.3 - # via - # nmsamplelocations (pyproject.toml) - # httpcore - # httpx - # pyproj - # requests - # sentry-sdk -cffi==1.17.1 - # via - # nmsamplelocations (pyproject.toml) - # cryptography -cfgv==3.4.0 - # via pre-commit -charset-normalizer==3.4.3 - # via - # nmsamplelocations (pyproject.toml) - # requests -click==8.2.1 - # via - # nmsamplelocations (pyproject.toml) - # uvicorn -cloud-sql-python-connector==1.18.4 - # via nmsamplelocations (pyproject.toml) -coverage==7.10.2 - # via pytest-cov -cryptography==45.0.6 - # via - # nmsamplelocations (pyproject.toml) - # authlib - # cloud-sql-python-connector -distlib==0.4.0 - # via virtualenv -dnspython==2.7.0 - # via - # nmsamplelocations (pyproject.toml) - # cloud-sql-python-connector - # email-validator -dotenv==0.9.9 - # via nmsamplelocations (pyproject.toml) -ecdsa==0.19.1 - # via python-jose -email-validator==2.2.0 - # via nmsamplelocations (pyproject.toml) -fastapi==0.116.1 - # via - # nmsamplelocations (pyproject.toml) - # fastapi-pagination - # sentry-sdk -fastapi-pagination==0.13.3 - # via nmsamplelocations (pyproject.toml) -filelock==3.18.0 - # via virtualenv -frozenlist==1.7.0 - # via - # nmsamplelocations (pyproject.toml) - # aiohttp - # aiosignal -geoalchemy2==0.18.0 - # via nmsamplelocations (pyproject.toml) -google-api-core==2.25.1 - # via - # nmsamplelocations (pyproject.toml) - # google-cloud-core - # google-cloud-storage -google-auth==2.40.3 - # via - # nmsamplelocations (pyproject.toml) - # cloud-sql-python-connector - # google-api-core - # google-cloud-core - # google-cloud-storage -google-cloud-core==2.4.3 - # via - # nmsamplelocations (pyproject.toml) - # google-cloud-storage -google-cloud-storage==3.2.0 - # via nmsamplelocations (pyproject.toml) -google-crc32c==1.7.1 - # via - # nmsamplelocations (pyproject.toml) - # google-cloud-storage - # google-resumable-media -google-resumable-media==2.7.2 - # via - # nmsamplelocations (pyproject.toml) - # google-cloud-storage -googleapis-common-protos==1.70.0 - # via - # nmsamplelocations (pyproject.toml) - # google-api-core -greenlet==3.2.4 - # via nmsamplelocations (pyproject.toml) -gunicorn==23.0.0 - # via nmsamplelocations (pyproject.toml) -h11==0.16.0 - # via - # nmsamplelocations (pyproject.toml) - # httpcore - # uvicorn -httpcore==1.0.9 - # via - # nmsamplelocations (pyproject.toml) - # httpx -httpx==0.28.1 - # via nmsamplelocations (pyproject.toml) -identify==2.6.12 - # via pre-commit -idna==3.10 - # via - # nmsamplelocations (pyproject.toml) - # anyio - # email-validator - # httpx - # requests - # yarl -iniconfig==2.1.0 - # via - # nmsamplelocations (pyproject.toml) - # pytest -itsdangerous==2.2.0 - # via nmsamplelocations (pyproject.toml) -jinja2==3.1.6 - # via nmsamplelocations (pyproject.toml) -mako==1.3.10 - # via - # nmsamplelocations (pyproject.toml) - # alembic -markupsafe==3.0.2 - # via - # nmsamplelocations (pyproject.toml) - # jinja2 - # mako -multidict==6.6.3 - # via - # nmsamplelocations (pyproject.toml) - # aiohttp - # yarl -nodeenv==1.9.1 - # via pre-commit -numpy==2.3.2 - # via - # nmsamplelocations (pyproject.toml) - # pandas - # pandas-stubs - # shapely -packaging==25.0 - # via - # nmsamplelocations (pyproject.toml) - # geoalchemy2 - # gunicorn - # pytest -pandas==2.3.1 - # via nmsamplelocations (pyproject.toml) -pandas-stubs==2.3.0.250703 - # via nmsamplelocations (pyproject.toml) -pg8000==1.31.4 - # via nmsamplelocations (pyproject.toml) -phonenumbers==9.0.12 - # via nmsamplelocations (pyproject.toml) -pillow==11.3.0 - # via nmsamplelocations (pyproject.toml) -platformdirs==4.3.8 - # via virtualenv -pluggy==1.6.0 - # via - # nmsamplelocations (pyproject.toml) - # pytest - # pytest-cov -pre-commit==4.3.0 - # via nmsamplelocations (pyproject.toml) -propcache==0.3.2 - # via - # nmsamplelocations (pyproject.toml) - # aiohttp - # yarl -proto-plus==1.26.1 - # via - # nmsamplelocations (pyproject.toml) - # google-api-core -protobuf==6.32.0 - # via - # nmsamplelocations (pyproject.toml) - # google-api-core - # googleapis-common-protos - # proto-plus -psycopg2==2.9.10 - # via nmsamplelocations (pyproject.toml) -pyasn1==0.6.1 - # via - # nmsamplelocations (pyproject.toml) - # pyasn1-modules - # python-jose - # rsa -pyasn1-modules==0.4.2 - # via - # nmsamplelocations (pyproject.toml) - # google-auth -pycparser==2.22 - # via - # nmsamplelocations (pyproject.toml) - # cffi -pydantic==2.11.7 - # via - # nmsamplelocations (pyproject.toml) - # fastapi - # fastapi-pagination -pydantic-core==2.33.2 - # via - # nmsamplelocations (pyproject.toml) - # pydantic -pygments==2.19.2 - # via - # nmsamplelocations (pyproject.toml) - # pytest -pyjwt==2.10.1 - # via nmsamplelocations (pyproject.toml) -pyproj==3.7.1 - # via nmsamplelocations (pyproject.toml) -pyshp==2.3.1 - # via nmsamplelocations (pyproject.toml) -pytest==8.4.1 - # via - # nmsamplelocations (pyproject.toml) - # pytest-cov -pytest-cov==6.2.1 - # via nmsamplelocations (pyproject.toml) -python-dateutil==2.9.0.post0 - # via - # nmsamplelocations (pyproject.toml) - # pandas - # pg8000 -python-dotenv==1.1.1 - # via dotenv -python-jose==3.5.0 - # via nmsamplelocations (pyproject.toml) -python-multipart==0.0.20 - # via nmsamplelocations (pyproject.toml) -pytz==2025.2 - # via - # nmsamplelocations (pyproject.toml) - # pandas -pyyaml==6.0.2 - # via pre-commit -requests==2.32.4 - # via - # nmsamplelocations (pyproject.toml) - # cloud-sql-python-connector - # google-api-core - # google-cloud-storage -rsa==4.9.1 - # via - # nmsamplelocations (pyproject.toml) - # google-auth - # python-jose -scramp==1.4.6 - # via - # nmsamplelocations (pyproject.toml) - # pg8000 -sentry-sdk==2.35.0 - # via nmsamplelocations (pyproject.toml) -shapely==2.1.1 - # via nmsamplelocations (pyproject.toml) -six==1.17.0 - # via - # nmsamplelocations (pyproject.toml) - # ecdsa - # python-dateutil -sniffio==1.3.1 - # via - # nmsamplelocations (pyproject.toml) - # anyio -sqlalchemy==2.0.43 - # via - # nmsamplelocations (pyproject.toml) - # alembic - # geoalchemy2 - # sqlalchemy-continuum - # sqlalchemy-searchable - # sqlalchemy-utils -sqlalchemy-continuum==1.4.2 - # via nmsamplelocations (pyproject.toml) -sqlalchemy-searchable==2.1.0 - # via nmsamplelocations (pyproject.toml) -sqlalchemy-utils==0.41.2 - # via - # nmsamplelocations (pyproject.toml) - # sqlalchemy-continuum - # sqlalchemy-searchable -starlette==0.47.2 - # via - # nmsamplelocations (pyproject.toml) - # fastapi -types-pytz==2025.2.0.20250809 - # via pandas-stubs -typing-extensions==4.14.1 - # via - # nmsamplelocations (pyproject.toml) - # aiosqlite - # alembic - # fastapi - # fastapi-pagination - # pydantic - # pydantic-core - # sqlalchemy - # typing-inspection -typing-inspection==0.4.1 - # via - # nmsamplelocations (pyproject.toml) - # pydantic -tzdata==2025.2 - # via - # nmsamplelocations (pyproject.toml) - # pandas -urllib3==2.5.0 - # via - # nmsamplelocations (pyproject.toml) - # requests - # sentry-sdk -uvicorn==0.35.0 - # via nmsamplelocations (pyproject.toml) -virtualenv==20.32.0 - # via pre-commit -yarl==1.20.1 - # via - # nmsamplelocations (pyproject.toml) - # aiohttp +anyio @ file:///home/conda/feedstock_root/build_artifacts/anyio_1717693030552/work +archspec @ file:///home/conda/feedstock_root/build_artifacts/archspec_1708969572489/work +boltons @ file:///home/conda/feedstock_root/build_artifacts/boltons_1711936407380/work +Brotli @ file:///Users/runner/miniforge3/conda-bld/brotli-split_1725267563793/work +brotlipy @ file:///Users/runner/miniforge3/conda-bld/brotlipy_1725393358468/work +certifi @ file:///home/conda/feedstock_root/build_artifacts/certifi_1754231422783/work/certifi +cffi @ file:///Users/runner/miniforge3/conda-bld/cffi_1725560578465/work +chardet @ file:///home/conda/feedstock_root/build_artifacts/chardet_1741797914774/work +charset-normalizer @ file:///home/conda/feedstock_root/build_artifacts/charset-normalizer_1698833585322/work +colorama @ file:///home/conda/feedstock_root/build_artifacts/colorama_1666700638685/work +conda @ file:///Users/runner/miniforge3/conda-bld/conda_1754405355057/work/conda-src +conda-libmamba-solver @ file:///home/conda/feedstock_root/build_artifacts/conda-libmamba-solver_1745834476052/work/src +conda-package-handling @ file:///home/conda/feedstock_root/build_artifacts/conda-package-handling_1717678605937/work +conda_package_streaming @ file:///home/conda/feedstock_root/build_artifacts/conda-package-streaming_1717678526951/work +crcmod==1.7 +cryptography @ file:///Users/runner/miniforge3/conda-bld/cryptography-split_1717559469549/work +distro @ file:///home/conda/feedstock_root/build_artifacts/distro_1704321475663/work +exceptiongroup @ file:///home/conda/feedstock_root/build_artifacts/exceptiongroup_1704921103267/work +frozendict @ file:///Users/runner/miniforge3/conda-bld/frozendict_1756048002891/work +h11 @ file:///home/conda/feedstock_root/build_artifacts/h11_1664132893548/work +h2 @ file:///home/conda/feedstock_root/build_artifacts/h2_1634280454336/work +hpack==4.0.0 +httpcore @ file:///home/conda/feedstock_root/build_artifacts/httpcore_1711596990900/work +httpx @ file:///home/conda/feedstock_root/build_artifacts/httpx_1708530890843/work +hyperframe @ file:///home/conda/feedstock_root/build_artifacts/hyperframe_1619110129307/work +idna @ file:///home/conda/feedstock_root/build_artifacts/idna_1713279365350/work +jsonpatch @ file:///home/conda/feedstock_root/build_artifacts/jsonpatch_1695536281965/work +jsonpointer @ file:///Users/runner/miniforge3/conda-bld/jsonpointer_1725302931775/work +libmambapy @ file:///Users/runner/miniforge3/conda-bld/mamba-split_1730277572706/work/libmambapy +menuinst @ file:///Users/runner/miniforge3/conda-bld/menuinst_1756243597479/work +packaging @ file:///home/conda/feedstock_root/build_artifacts/packaging_1718189413536/work +platformdirs @ file:///home/conda/feedstock_root/build_artifacts/platformdirs_1715777629804/work +pluggy @ file:///home/conda/feedstock_root/build_artifacts/pluggy_1713667077545/work +psycopg-binary==3.2.9 +pycosat @ file:///Users/runner/miniforge3/conda-bld/pycosat_1732588505673/work +pycparser @ file:///home/conda/feedstock_root/build_artifacts/pycparser_1711811537435/work +pyOpenSSL @ file:///home/conda/feedstock_root/build_artifacts/pyopenssl_1706660063483/work +PySocks @ file:///home/conda/feedstock_root/build_artifacts/pysocks_1661604839144/work +requests @ file:///home/conda/feedstock_root/build_artifacts/requests_1717057054362/work +ruamel-yaml-conda @ file:///Users/runner/miniforge3/conda-bld/ruamel_yaml_1695546298062/work +ruamel.yaml @ file:///Users/runner/miniforge3/conda-bld/ruamel.yaml_1755625084805/work +ruamel.yaml.clib @ file:///Users/runner/miniforge3/conda-bld/ruamel.yaml.clib_1728724504988/work +setuptools==70.1.1 +six @ file:///home/conda/feedstock_root/build_artifacts/six_1620240208055/work +sniffio @ file:///home/conda/feedstock_root/build_artifacts/sniffio_1708952932303/work +tqdm @ file:///home/conda/feedstock_root/build_artifacts/tqdm_1714854870413/work +truststore @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_truststore_1753886790/work +typing_extensions @ file:///home/conda/feedstock_root/build_artifacts/typing_extensions_1717802530399/work +urllib3 @ file:///home/conda/feedstock_root/build_artifacts/urllib3_1719391292974/work +wheel==0.43.0 +zstandard==0.23.0 diff --git a/uv.lock b/uv.lock index d2c6db9b5..562872de4 100644 --- a/uv.lock +++ b/uv.lock @@ -942,6 +942,7 @@ dependencies = [ { name = "propcache" }, { name = "proto-plus" }, { name = "protobuf" }, + { name = "psycopg-binary" }, { name = "psycopg2" }, { name = "pyasn1" }, { name = "pyasn1-modules" }, @@ -1046,6 +1047,7 @@ requires-dist = [ { name = "propcache", specifier = "==0.3.2" }, { name = "proto-plus", specifier = "==1.26.1" }, { name = "protobuf", specifier = "==6.32.0" }, + { name = "psycopg-binary", specifier = ">=3.2.9" }, { name = "psycopg2", specifier = "==2.9.10" }, { name = "pyasn1", specifier = "==0.6.1" }, { name = "pyasn1-modules", specifier = "==0.4.2" }, @@ -1416,6 +1418,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, ] +[[package]] +name = "psycopg-binary" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127, upload-time = "2025-05-13T16:08:21.391Z" }, + { url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322, upload-time = "2025-05-13T16:08:24.049Z" }, + { url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097, upload-time = "2025-05-13T16:08:27.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114, upload-time = "2025-05-13T16:08:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693, upload-time = "2025-05-13T16:08:34.625Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423, upload-time = "2025-05-13T16:08:37.444Z" }, + { url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667, upload-time = "2025-05-13T16:08:40.116Z" }, + { url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576, upload-time = "2025-05-13T16:08:43.243Z" }, + { url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439, upload-time = "2025-05-13T16:08:47.321Z" }, + { url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477, upload-time = "2025-05-13T16:08:51.166Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009, upload-time = "2025-05-13T16:08:53.67Z" }, +] + [[package]] name = "psycopg2" version = "2.9.10" From e182688ba3411ef9bb7eb49d60ed96e6a9432cf3 Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 14:56:56 -0600 Subject: [PATCH 21/46] feat: simplify requirements.txt by removing direct file references --- requirements.txt | 90 ++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/requirements.txt b/requirements.txt index 8f756f98e..ebb732c65 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,51 +1,51 @@ -anyio @ file:///home/conda/feedstock_root/build_artifacts/anyio_1717693030552/work -archspec @ file:///home/conda/feedstock_root/build_artifacts/archspec_1708969572489/work -boltons @ file:///home/conda/feedstock_root/build_artifacts/boltons_1711936407380/work -Brotli @ file:///Users/runner/miniforge3/conda-bld/brotli-split_1725267563793/work -brotlipy @ file:///Users/runner/miniforge3/conda-bld/brotlipy_1725393358468/work -certifi @ file:///home/conda/feedstock_root/build_artifacts/certifi_1754231422783/work/certifi -cffi @ file:///Users/runner/miniforge3/conda-bld/cffi_1725560578465/work -chardet @ file:///home/conda/feedstock_root/build_artifacts/chardet_1741797914774/work -charset-normalizer @ file:///home/conda/feedstock_root/build_artifacts/charset-normalizer_1698833585322/work -colorama @ file:///home/conda/feedstock_root/build_artifacts/colorama_1666700638685/work -conda @ file:///Users/runner/miniforge3/conda-bld/conda_1754405355057/work/conda-src -conda-libmamba-solver @ file:///home/conda/feedstock_root/build_artifacts/conda-libmamba-solver_1745834476052/work/src -conda-package-handling @ file:///home/conda/feedstock_root/build_artifacts/conda-package-handling_1717678605937/work -conda_package_streaming @ file:///home/conda/feedstock_root/build_artifacts/conda-package-streaming_1717678526951/work +anyio +archspec +boltons +Brotli +brotlipy +certifi +cffi +chardet +charset-normalizer +colorama +conda +conda-libmamba-solver +conda-package-handling +conda_package_streaming crcmod==1.7 -cryptography @ file:///Users/runner/miniforge3/conda-bld/cryptography-split_1717559469549/work -distro @ file:///home/conda/feedstock_root/build_artifacts/distro_1704321475663/work -exceptiongroup @ file:///home/conda/feedstock_root/build_artifacts/exceptiongroup_1704921103267/work -frozendict @ file:///Users/runner/miniforge3/conda-bld/frozendict_1756048002891/work -h11 @ file:///home/conda/feedstock_root/build_artifacts/h11_1664132893548/work -h2 @ file:///home/conda/feedstock_root/build_artifacts/h2_1634280454336/work +cryptography +distro +exceptiongroup +frozendict +h11 +h2 hpack==4.0.0 -httpcore @ file:///home/conda/feedstock_root/build_artifacts/httpcore_1711596990900/work -httpx @ file:///home/conda/feedstock_root/build_artifacts/httpx_1708530890843/work -hyperframe @ file:///home/conda/feedstock_root/build_artifacts/hyperframe_1619110129307/work -idna @ file:///home/conda/feedstock_root/build_artifacts/idna_1713279365350/work -jsonpatch @ file:///home/conda/feedstock_root/build_artifacts/jsonpatch_1695536281965/work -jsonpointer @ file:///Users/runner/miniforge3/conda-bld/jsonpointer_1725302931775/work -libmambapy @ file:///Users/runner/miniforge3/conda-bld/mamba-split_1730277572706/work/libmambapy -menuinst @ file:///Users/runner/miniforge3/conda-bld/menuinst_1756243597479/work -packaging @ file:///home/conda/feedstock_root/build_artifacts/packaging_1718189413536/work -platformdirs @ file:///home/conda/feedstock_root/build_artifacts/platformdirs_1715777629804/work -pluggy @ file:///home/conda/feedstock_root/build_artifacts/pluggy_1713667077545/work +httpcore +httpx +hyperframe +idna +jsonpatch +jsonpointer +libmambapy +menuinst +packaging +platformdirs +pluggy psycopg-binary==3.2.9 -pycosat @ file:///Users/runner/miniforge3/conda-bld/pycosat_1732588505673/work -pycparser @ file:///home/conda/feedstock_root/build_artifacts/pycparser_1711811537435/work -pyOpenSSL @ file:///home/conda/feedstock_root/build_artifacts/pyopenssl_1706660063483/work -PySocks @ file:///home/conda/feedstock_root/build_artifacts/pysocks_1661604839144/work -requests @ file:///home/conda/feedstock_root/build_artifacts/requests_1717057054362/work -ruamel-yaml-conda @ file:///Users/runner/miniforge3/conda-bld/ruamel_yaml_1695546298062/work -ruamel.yaml @ file:///Users/runner/miniforge3/conda-bld/ruamel.yaml_1755625084805/work -ruamel.yaml.clib @ file:///Users/runner/miniforge3/conda-bld/ruamel.yaml.clib_1728724504988/work +pycosat +pycparser +pyOpenSSL +PySocks +requests +ruamel-yaml-conda +ruamel.yaml +ruamel.yaml.clib setuptools==70.1.1 -six @ file:///home/conda/feedstock_root/build_artifacts/six_1620240208055/work -sniffio @ file:///home/conda/feedstock_root/build_artifacts/sniffio_1708952932303/work -tqdm @ file:///home/conda/feedstock_root/build_artifacts/tqdm_1714854870413/work -truststore @ file:///home/conda/feedstock_root/build_artifacts/bld/rattler-build_truststore_1753886790/work -typing_extensions @ file:///home/conda/feedstock_root/build_artifacts/typing_extensions_1717802530399/work -urllib3 @ file:///home/conda/feedstock_root/build_artifacts/urllib3_1719391292974/work +six +sniffio +tqdm +truststore +typing_extensions +urllib3 wheel==0.43.0 zstandard==0.23.0 From 3c1b91a3a0139e9a3eedb44142c86e2966161a1f Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 14:59:02 -0600 Subject: [PATCH 22/46] feat: use uv requirements --- requirements.txt | 1214 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 1163 insertions(+), 51 deletions(-) diff --git a/requirements.txt b/requirements.txt index ebb732c65..64ecfd934 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,51 +1,1163 @@ -anyio -archspec -boltons -Brotli -brotlipy -certifi -cffi -chardet -charset-normalizer -colorama -conda -conda-libmamba-solver -conda-package-handling -conda_package_streaming -crcmod==1.7 -cryptography -distro -exceptiongroup -frozendict -h11 -h2 -hpack==4.0.0 -httpcore -httpx -hyperframe -idna -jsonpatch -jsonpointer -libmambapy -menuinst -packaging -platformdirs -pluggy -psycopg-binary==3.2.9 -pycosat -pycparser -pyOpenSSL -PySocks -requests -ruamel-yaml-conda -ruamel.yaml -ruamel.yaml.clib -setuptools==70.1.1 -six -sniffio -tqdm -truststore -typing_extensions -urllib3 -wheel==0.43.0 -zstandard==0.23.0 +# This file was autogenerated by uv via the following command: +# uv export --format requirements-txt +aiofiles==24.1.0 \ + --hash=sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c \ + --hash=sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5 + # via + # cloud-sql-python-connector + # nmsamplelocations +aiohappyeyeballs==2.6.1 \ + --hash=sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558 \ + --hash=sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8 + # via + # aiohttp + # nmsamplelocations +aiohttp==3.12.15 \ + --hash=sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645 \ + --hash=sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84 \ + --hash=sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd \ + --hash=sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4 \ + --hash=sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693 \ + --hash=sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2 \ + --hash=sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d \ + --hash=sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b \ + --hash=sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64 \ + --hash=sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d \ + --hash=sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9 \ + --hash=sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315 \ + --hash=sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d \ + --hash=sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51 \ + --hash=sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461 \ + --hash=sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7 \ + --hash=sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d \ + --hash=sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0 + # via + # cloud-sql-python-connector + # nmsamplelocations +aiosignal==1.4.0 \ + --hash=sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e \ + --hash=sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7 + # via + # aiohttp + # nmsamplelocations +aiosqlite==0.21.0 \ + --hash=sha256:131bb8056daa3bc875608c631c678cda73922a2d4ba8aec373b19f18c17e7aa3 \ + --hash=sha256:2549cf4057f95f53dcba16f2b64e8e2791d7e1adedb13197dd8ed77bb226d7d0 + # via nmsamplelocations +alembic==1.16.4 \ + --hash=sha256:b05e51e8e82efc1abd14ba2af6392897e145930c3e0a2faf2b0da2f7f7fd660d \ + --hash=sha256:efab6ada0dd0fae2c92060800e0bf5c1dc26af15a10e02fb4babff164b4725e2 + # via nmsamplelocations +annotated-types==0.7.0 \ + --hash=sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53 \ + --hash=sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89 + # via + # nmsamplelocations + # pydantic +anyio==4.10.0 \ + --hash=sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6 \ + --hash=sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1 + # via + # httpx + # nmsamplelocations + # starlette +asgiref==3.9.1 \ + --hash=sha256:a5ab6582236218e5ef1648f242fd9f10626cfd4de8dc377db215d5d5098e3142 \ + --hash=sha256:f3bba7092a48005b5f5bacd747d36ee4a5a61f4a269a6df590b43144355ebd2c + # via nmsamplelocations +asn1crypto==1.5.1 \ + --hash=sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c \ + --hash=sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67 + # via + # nmsamplelocations + # scramp +asyncpg==0.30.0 \ + --hash=sha256:04ff0785ae7eed6cc138e73fc67b8e51d54ee7a3ce9b63666ce55a0bf095f7ba \ + --hash=sha256:05b185ebb8083c8568ea8a40e896d5f7af4b8554b64d7719c0eaa1eb5a5c3a70 \ + --hash=sha256:46973045b567972128a27d40001124fbc821c87a6cade040cfcd4fa8a30bcdc4 \ + --hash=sha256:9110df111cabc2ed81aad2f35394a00cadf4f2e0635603db6ebbd0fc896f46a4 \ + --hash=sha256:9b6fde867a74e8c76c71e2f64f80c64c0f3163e687f1763cfaf21633ec24ec33 \ + --hash=sha256:ae374585f51c2b444510cdf3595b97ece4f233fde739aa14b50e0d64e8a7a590 \ + --hash=sha256:c47806b1a8cbb0a0db896f4cd34d89942effe353a5035c62734ab13b9f938da3 \ + --hash=sha256:c551e9928ab6707602f44811817f82ba3c446e018bfe1d3abecc8ba5f3eac851 \ + --hash=sha256:f59b430b8e27557c3fb9869222559f7417ced18688375825f8f12302c34e915e + # via nmsamplelocations +attrs==25.3.0 \ + --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \ + --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b + # via + # aiohttp + # nmsamplelocations +authlib==1.6.1 \ + --hash=sha256:4dffdbb1460ba6ec8c17981a4c67af7d8af131231b5a36a88a1e8c80c111cdfd \ + --hash=sha256:e9d2031c34c6309373ab845afc24168fe9e93dc52d252631f52642f21f5ed06e + # via nmsamplelocations +bcrypt==4.3.0 \ + --hash=sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f \ + --hash=sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d \ + --hash=sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24 \ + --hash=sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3 \ + --hash=sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c \ + --hash=sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd \ + --hash=sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f \ + --hash=sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f \ + --hash=sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d \ + --hash=sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe \ + --hash=sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231 \ + --hash=sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef \ + --hash=sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18 \ + --hash=sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f \ + --hash=sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e \ + --hash=sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732 \ + --hash=sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304 \ + --hash=sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0 \ + --hash=sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62 \ + --hash=sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180 \ + --hash=sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af \ + --hash=sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669 \ + --hash=sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761 \ + --hash=sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51 \ + --hash=sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23 \ + --hash=sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09 \ + --hash=sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505 \ + --hash=sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4 \ + --hash=sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753 \ + --hash=sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59 \ + --hash=sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b \ + --hash=sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d \ + --hash=sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b \ + --hash=sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a \ + --hash=sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb \ + --hash=sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb \ + --hash=sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676 \ + --hash=sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b \ + --hash=sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe \ + --hash=sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281 \ + --hash=sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1 \ + --hash=sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef \ + --hash=sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d + # via nmsamplelocations +cachetools==5.5.2 \ + --hash=sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4 \ + --hash=sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a + # via + # google-auth + # nmsamplelocations +certifi==2025.8.3 \ + --hash=sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407 \ + --hash=sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5 + # via + # httpcore + # httpx + # nmsamplelocations + # pyproj + # requests + # sentry-sdk +cffi==1.17.1 \ + --hash=sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2 \ + --hash=sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824 \ + --hash=sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed \ + --hash=sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683 \ + --hash=sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9 \ + --hash=sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4 \ + --hash=sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3 \ + --hash=sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd \ + --hash=sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5 \ + --hash=sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d \ + --hash=sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e \ + --hash=sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a + # via + # cryptography + # nmsamplelocations + # pact-python +cfgv==3.4.0 \ + --hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \ + --hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560 + # via pre-commit +charset-normalizer==3.4.3 \ + --hash=sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe \ + --hash=sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc \ + --hash=sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa \ + --hash=sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9 \ + --hash=sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d \ + --hash=sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92 \ + --hash=sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31 \ + --hash=sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15 \ + --hash=sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f \ + --hash=sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8 \ + --hash=sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0 \ + --hash=sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927 \ + --hash=sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce \ + --hash=sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14 \ + --hash=sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c \ + --hash=sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096 \ + --hash=sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db \ + --hash=sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5 \ + --hash=sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce \ + --hash=sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049 \ + --hash=sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a \ + --hash=sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef \ + --hash=sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16 \ + --hash=sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9 + # via + # nmsamplelocations + # requests +click==8.2.1 \ + --hash=sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202 \ + --hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b + # via + # nmsamplelocations + # pact-python + # uvicorn +cloud-sql-python-connector==1.18.4 \ + --hash=sha256:0a77a16ab2d93fc78d8593175cb69fedfbc1c67aa99f9b3ba70b5026343db092 \ + --hash=sha256:dd2b015245d77771b5e7566e2817e279e9daca90e0cf30dac032155e813afe76 + # via nmsamplelocations +colorama==0.4.6 ; sys_platform == 'win32' \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via + # click + # pytest +coverage==7.10.2 \ + --hash=sha256:0100b19f230df72c90fdb36db59d3f39232391e8d89616a7de30f677da4f532b \ + --hash=sha256:04c74f9ef1f925456a9fd23a7eef1103126186d0500ef9a0acb0bd2514bdc7cc \ + --hash=sha256:11333094c1bff621aa811b67ed794865cbcaa99984dedea4bd9cf780ad64ecba \ + --hash=sha256:12e52b5aa00aa720097d6947d2eb9e404e7c1101ad775f9661ba165ed0a28303 \ + --hash=sha256:14fb5b6641ab5b3c4161572579f0f2ea8834f9d3af2f7dd8fbaecd58ef9175cc \ + --hash=sha256:1a2e934e9da26341d342d30bfe91422bbfdb3f1f069ec87f19b2909d10d8dcc4 \ + --hash=sha256:228946da741558904e2c03ce870ba5efd9cd6e48cbc004d9a27abee08100a15a \ + --hash=sha256:248b5394718e10d067354448dc406d651709c6765669679311170da18e0e9af8 \ + --hash=sha256:2d358f259d8019d4ef25d8c5b78aca4c7af25e28bd4231312911c22a0e824a57 \ + --hash=sha256:2e980e4179f33d9b65ac4acb86c9c0dde904098853f27f289766657ed16e07b3 \ + --hash=sha256:5250bda76e30382e0a2dcd68d961afcab92c3a7613606e6269855c6979a1b0bb \ + --hash=sha256:52d708b5fd65589461381fa442d9905f5903d76c086c6a4108e8e9efdca7a7ed \ + --hash=sha256:5b9d538e8e04916a5df63052d698b30c74eb0174f2ca9cd942c981f274a18eaf \ + --hash=sha256:5c61675a922b569137cf943770d7ad3edd0202d992ce53ac328c5ff68213ccf4 \ + --hash=sha256:5d6e6d84e6dd31a8ded64759626627247d676a23c1b892e1326f7c55c8d61055 \ + --hash=sha256:651015dcd5fd9b5a51ca79ece60d353cacc5beaf304db750407b29c89f72fe2b \ + --hash=sha256:65b451949cb789c346f9f9002441fc934d8ccedcc9ec09daabc2139ad13853f7 \ + --hash=sha256:6eb586fa7d2aee8d65d5ae1dd71414020b2f447435c57ee8de8abea0a77d5074 \ + --hash=sha256:718044729bf1fe3e9eb9f31b52e44ddae07e434ec050c8c628bf5adc56fe4bdd \ + --hash=sha256:71d40b3ac0f26fa9ffa6ee16219a714fed5c6ec197cdcd2018904ab5e75bcfa3 \ + --hash=sha256:75cc1a3f8c88c69bf16a871dab1fe5a7303fdb1e9f285f204b60f1ee539b8fc0 \ + --hash=sha256:81bf6a32212f9f66da03d63ecb9cd9bd48e662050a937db7199dbf47d19831de \ + --hash=sha256:835f39e618099325e7612b3406f57af30ab0a0af350490eff6421e2e5f608e46 \ + --hash=sha256:8f34b09f68bdadec122ffad312154eda965ade433559cc1eadd96cca3de5c824 \ + --hash=sha256:916369b3b914186b2c5e5ad2f7264b02cff5df96cdd7cdad65dccd39aa5fd9f0 \ + --hash=sha256:95db3750dd2e6e93d99fa2498f3a1580581e49c494bddccc6f85c5c21604921f \ + --hash=sha256:95e23987b52d02e7c413bf2d6dc6288bd5721beb518052109a13bfdc62c8033b \ + --hash=sha256:96e5921342574a14303dfdb73de0019e1ac041c863743c8fe1aa6c2b4a257226 \ + --hash=sha256:9c1cd71483ea78331bdfadb8dcec4f4edfb73c7002c1206d8e0af6797853f5be \ + --hash=sha256:9f75dbf4899e29a37d74f48342f29279391668ef625fdac6d2f67363518056a1 \ + --hash=sha256:a3e853cc04987c85ec410905667eed4bf08b1d84d80dfab2684bb250ac8da4f6 \ + --hash=sha256:a7df481e7508de1c38b9b8043da48d94931aefa3e32b47dd20277e4978ed5b95 \ + --hash=sha256:a91e027d66eff214d88d9afbe528e21c9ef1ecdf4956c46e366c50f3094696d0 \ + --hash=sha256:abb57fdd38bf6f7dcc66b38dafb7af7c5fdc31ac6029ce373a6f7f5331d6f60f \ + --hash=sha256:aca7b5645afa688de6d4f8e89d30c577f62956fefb1bad021490d63173874186 \ + --hash=sha256:c2e117e64c26300032755d4520cd769f2623cde1a1d1c3515b05a3b8add0ade1 \ + --hash=sha256:ca07fa78cc9d26bc8c4740de1abd3489cf9c47cc06d9a8ab3d552ff5101af4c0 \ + --hash=sha256:d800705f6951f75a905ea6feb03fff8f3ea3468b81e7563373ddc29aa3e5d1ca \ + --hash=sha256:daaf98009977f577b71f8800208f4d40d4dcf5c2db53d4d822787cdc198d76e1 \ + --hash=sha256:e8415918856a3e7d57a4e0ad94651b761317de459eb74d34cc1bb51aad80f07e \ + --hash=sha256:e96649ac34a3d0e6491e82a2af71098e43be2874b619547c3282fc11d3840a4b \ + --hash=sha256:ea8d8fe546c528535c761ba424410bbeb36ba8a0f24be653e94b70c93fd8a8ca \ + --hash=sha256:f256173b48cc68486299d510a3e729a96e62c889703807482dbf56946befb5c8 \ + --hash=sha256:f287a25a8ca53901c613498e4a40885b19361a2fe8fbfdbb7f8ef2cad2a23f03 \ + --hash=sha256:f35481d42c6d146d48ec92d4e239c23f97b53a3f1fbd2302e7c64336f28641fe \ + --hash=sha256:fe024d40ac31eb8d5aae70215b41dafa264676caa4404ae155f77d2fa95c37bb + # via pytest-cov +cryptography==45.0.6 \ + --hash=sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5 \ + --hash=sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74 \ + --hash=sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394 \ + --hash=sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301 \ + --hash=sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08 \ + --hash=sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3 \ + --hash=sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b \ + --hash=sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402 \ + --hash=sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3 \ + --hash=sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0 \ + --hash=sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f \ + --hash=sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3 \ + --hash=sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9 \ + --hash=sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5 \ + --hash=sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719 \ + --hash=sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02 \ + --hash=sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2 \ + --hash=sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec \ + --hash=sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159 \ + --hash=sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453 \ + --hash=sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf \ + --hash=sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9 \ + --hash=sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016 \ + --hash=sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05 \ + --hash=sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42 + # via + # authlib + # cloud-sql-python-connector + # nmsamplelocations +distlib==0.4.0 \ + --hash=sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16 \ + --hash=sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d + # via virtualenv +dnspython==2.7.0 \ + --hash=sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86 \ + --hash=sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1 + # via + # cloud-sql-python-connector + # email-validator + # nmsamplelocations +dotenv==0.9.9 \ + --hash=sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9 + # via nmsamplelocations +ecdsa==0.19.1 \ + --hash=sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3 \ + --hash=sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61 + # via python-jose +email-validator==2.2.0 \ + --hash=sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631 \ + --hash=sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7 + # via nmsamplelocations +fastapi==0.116.1 \ + --hash=sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565 \ + --hash=sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143 + # via + # fastapi-pagination + # nmsamplelocations + # pact-python + # sentry-sdk +fastapi-pagination==0.14.0 \ + --hash=sha256:72ed471c79c3aa07940b287b9cf022503ec2e69e1f2ca5a7a5fcd8152cc43392 \ + --hash=sha256:e576800270f2714c781241b66c11f5da24f6671670edc122f5be7e4c2bd7f353 + # via nmsamplelocations +filelock==3.18.0 \ + --hash=sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2 \ + --hash=sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de + # via virtualenv +frozenlist==1.7.0 \ + --hash=sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f \ + --hash=sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b \ + --hash=sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949 \ + --hash=sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf \ + --hash=sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f \ + --hash=sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c \ + --hash=sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c \ + --hash=sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81 \ + --hash=sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e \ + --hash=sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657 \ + --hash=sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca \ + --hash=sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104 \ + --hash=sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba \ + --hash=sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1 \ + --hash=sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60 \ + --hash=sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee \ + --hash=sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb \ + --hash=sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d \ + --hash=sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00 \ + --hash=sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b \ + --hash=sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146 \ + --hash=sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e \ + --hash=sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3 \ + --hash=sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d \ + --hash=sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1 \ + --hash=sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384 \ + --hash=sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb \ + --hash=sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65 \ + --hash=sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43 \ + --hash=sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d \ + --hash=sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d \ + --hash=sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e \ + --hash=sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee \ + --hash=sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1 \ + --hash=sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74 \ + --hash=sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b + # via + # aiohttp + # aiosignal + # nmsamplelocations +geoalchemy2==0.18.0 \ + --hash=sha256:9a04690cc33fbc580d15c7c028d9b1b1ea08271489730096c7092e1d486c2b7a \ + --hash=sha256:ff0fe7339ba535c50845a2c7e8817a20c164364128991d795733b3c5904b1ee1 + # via nmsamplelocations +google-api-core==2.25.1 \ + --hash=sha256:8a2a56c1fef82987a524371f99f3bd0143702fecc670c72e600c1cda6bf8dbb7 \ + --hash=sha256:d2aaa0b13c78c61cb3f4282c464c046e45fbd75755683c9c525e6e8f7ed0a5e8 + # via + # google-cloud-core + # google-cloud-storage + # nmsamplelocations +google-auth==2.40.3 \ + --hash=sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca \ + --hash=sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77 + # via + # cloud-sql-python-connector + # google-api-core + # google-cloud-core + # google-cloud-storage + # nmsamplelocations +google-cloud-core==2.4.3 \ + --hash=sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53 \ + --hash=sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e + # via + # google-cloud-storage + # nmsamplelocations +google-cloud-storage==3.3.0 \ + --hash=sha256:0338ecd6621b3ecacb108f1cf7513ff0d1bca7f1ff4d58e0220b59f3a725ff23 \ + --hash=sha256:ae9d891d53e17d9681d7c4ef1ffeea0cde9bdc53d5b64fa6ff6bf30d1911cf61 + # via nmsamplelocations +google-crc32c==1.7.1 \ + --hash=sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db \ + --hash=sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472 \ + --hash=sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3 \ + --hash=sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6 \ + --hash=sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb \ + --hash=sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35 \ + --hash=sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9 \ + --hash=sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638 + # via + # google-cloud-storage + # google-resumable-media + # nmsamplelocations +google-resumable-media==2.7.2 \ + --hash=sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa \ + --hash=sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0 + # via + # google-cloud-storage + # nmsamplelocations +googleapis-common-protos==1.70.0 \ + --hash=sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257 \ + --hash=sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8 + # via + # google-api-core + # nmsamplelocations +greenlet==3.2.4 \ + --hash=sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b \ + --hash=sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735 \ + --hash=sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d \ + --hash=sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31 \ + --hash=sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671 \ + --hash=sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f \ + --hash=sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337 \ + --hash=sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0 \ + --hash=sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b \ + --hash=sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc \ + --hash=sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1 \ + --hash=sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5 \ + --hash=sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a \ + --hash=sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945 \ + --hash=sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae \ + --hash=sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504 \ + --hash=sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01 + # via + # nmsamplelocations + # sqlalchemy +gunicorn==23.0.0 \ + --hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \ + --hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec + # via nmsamplelocations +h11==0.16.0 \ + --hash=sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1 \ + --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86 + # via + # httpcore + # nmsamplelocations + # uvicorn +httpcore==1.0.9 \ + --hash=sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55 \ + --hash=sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8 + # via + # httpx + # nmsamplelocations +httpx==0.28.1 \ + --hash=sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc \ + --hash=sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad + # via nmsamplelocations +identify==2.6.12 \ + --hash=sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2 \ + --hash=sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6 + # via pre-commit +idna==3.10 \ + --hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \ + --hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3 + # via + # anyio + # email-validator + # httpx + # nmsamplelocations + # requests + # yarl +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via + # nmsamplelocations + # pytest +itsdangerous==2.2.0 \ + --hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \ + --hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173 + # via nmsamplelocations +jinja2==3.1.6 \ + --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ + --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 + # via nmsamplelocations +mako==1.3.10 \ + --hash=sha256:99579a6f39583fa7e5630a28c3c1f440e4e97a414b80372649c0ce338da2ea28 \ + --hash=sha256:baef24a52fc4fc514a0887ac600f9f1cff3d82c61d4d700a1fa84d597b88db59 + # via + # alembic + # nmsamplelocations +markupsafe==3.0.2 \ + --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ + --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ + --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ + --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ + --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ + --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ + --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ + --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ + --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ + --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ + --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ + --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ + --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ + --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ + --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ + --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ + --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ + --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ + --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ + --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ + --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 + # via + # jinja2 + # mako + # nmsamplelocations +multidict==6.6.3 \ + --hash=sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134 \ + --hash=sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e \ + --hash=sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f \ + --hash=sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc \ + --hash=sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c \ + --hash=sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7 \ + --hash=sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3 \ + --hash=sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55 \ + --hash=sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e \ + --hash=sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e \ + --hash=sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b \ + --hash=sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d \ + --hash=sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc \ + --hash=sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65 \ + --hash=sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884 \ + --hash=sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2 \ + --hash=sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a \ + --hash=sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca \ + --hash=sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6 \ + --hash=sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b \ + --hash=sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f \ + --hash=sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6 \ + --hash=sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d \ + --hash=sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373 \ + --hash=sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648 \ + --hash=sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1 \ + --hash=sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600 \ + --hash=sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb \ + --hash=sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8 \ + --hash=sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471 \ + --hash=sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0 \ + --hash=sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c \ + --hash=sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8 \ + --hash=sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9 \ + --hash=sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b \ + --hash=sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37 \ + --hash=sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c \ + --hash=sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1 + # via + # aiohttp + # nmsamplelocations + # yarl +nodeenv==1.9.1 \ + --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \ + --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 + # via pre-commit +numpy==2.3.2 \ + --hash=sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5 \ + --hash=sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b \ + --hash=sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631 \ + --hash=sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58 \ + --hash=sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b \ + --hash=sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089 \ + --hash=sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf \ + --hash=sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910 \ + --hash=sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91 \ + --hash=sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45 \ + --hash=sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a \ + --hash=sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e \ + --hash=sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab \ + --hash=sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2 \ + --hash=sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b \ + --hash=sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2 \ + --hash=sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee \ + --hash=sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1 \ + --hash=sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a \ + --hash=sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450 \ + --hash=sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a \ + --hash=sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2 \ + --hash=sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2 \ + --hash=sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125 \ + --hash=sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19 \ + --hash=sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b \ + --hash=sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f \ + --hash=sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2 \ + --hash=sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a \ + --hash=sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6 \ + --hash=sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286 \ + --hash=sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f \ + --hash=sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2 \ + --hash=sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0 \ + --hash=sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b \ + --hash=sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56 \ + --hash=sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5 \ + --hash=sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3 \ + --hash=sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0 \ + --hash=sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6 \ + --hash=sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8 \ + --hash=sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48 \ + --hash=sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b \ + --hash=sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0 \ + --hash=sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5 + # via + # nmsamplelocations + # pandas + # pandas-stubs + # shapely +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via + # geoalchemy2 + # gunicorn + # nmsamplelocations + # pytest +pact-python==2.3.3 \ + --hash=sha256:11433275e3b6d7eea8b0d328d3711a5c458575f69f50815027860a291d48f62f \ + --hash=sha256:5bfda6100b0a146c8238a0c822cab918936192e2ea4b03e48da4bedb93756510 \ + --hash=sha256:7c382743eb89a18b10d0b387a022fed1ec0cce7881bbddd409844e6e172af9e8 \ + --hash=sha256:c41ff6911cc054b11c79c1e7524f30ff676ee1aaa85dca1a4dff0e83ca98d2bd \ + --hash=sha256:c696cad249b23c8bf926664dd294b0bd6e5e7fd910dcc002f96998f1b6a37a8e \ + --hash=sha256:c6e4ef70894db51d876fb765bc97eb9118b8da880e4fc3a2eef11eb9977919ec \ + --hash=sha256:ee83357872fca5cc7ab07ff53ffc88065557787663188154f849c4edaa838ba4 \ + --hash=sha256:f939e673b0268d90c13aa6a6ab18b7a32dd44a7a6587de64a5f95a25f690293f + # via nmsamplelocations +pandas==2.3.1 \ + --hash=sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2 \ + --hash=sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956 \ + --hash=sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9 \ + --hash=sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab \ + --hash=sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444 \ + --hash=sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a \ + --hash=sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb \ + --hash=sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928 \ + --hash=sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a \ + --hash=sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22 \ + --hash=sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275 \ + --hash=sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12 \ + --hash=sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9 \ + --hash=sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96 + # via nmsamplelocations +pandas-stubs==2.3.0.250703 \ + --hash=sha256:a9265fc69909f0f7a9cabc5f596d86c9d531499fed86b7838fd3278285d76b81 \ + --hash=sha256:fb6a8478327b16ed65c46b1541de74f5c5947f3601850caf3e885e0140584717 + # via nmsamplelocations +pg8000==1.31.4 \ + --hash=sha256:d14fb2054642ee80f9a216721892e99e19db60a005358460ffa48872351423d4 \ + --hash=sha256:e7ecce4339891f27b0b22e2f79eb9efe44118bd384207359fc18350f788ace00 + # via nmsamplelocations +phonenumbers==9.0.12 \ + --hash=sha256:900633afc3e12191458d710262df5efc117838bd1e2e613b64fa254a86bb20a1 \ + --hash=sha256:ccadff6b949494bd606836d8c9678bee5b55cb1cbad1e98bf7adae108e6fd0be + # via nmsamplelocations +pillow==11.3.0 \ + --hash=sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2 \ + --hash=sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214 \ + --hash=sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59 \ + --hash=sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50 \ + --hash=sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632 \ + --hash=sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a \ + --hash=sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51 \ + --hash=sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced \ + --hash=sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12 \ + --hash=sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8 \ + --hash=sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6 \ + --hash=sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580 \ + --hash=sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd \ + --hash=sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8 \ + --hash=sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673 \ + --hash=sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788 \ + --hash=sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e \ + --hash=sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8 \ + --hash=sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523 \ + --hash=sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477 \ + --hash=sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027 \ + --hash=sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b \ + --hash=sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e \ + --hash=sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b \ + --hash=sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae \ + --hash=sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d \ + --hash=sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f \ + --hash=sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874 \ + --hash=sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa \ + --hash=sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd \ + --hash=sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c \ + --hash=sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31 \ + --hash=sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e \ + --hash=sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db \ + --hash=sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77 \ + --hash=sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a \ + --hash=sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b \ + --hash=sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635 \ + --hash=sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3 \ + --hash=sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe \ + --hash=sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805 \ + --hash=sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36 \ + --hash=sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12 \ + --hash=sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c \ + --hash=sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6 \ + --hash=sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1 \ + --hash=sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653 \ + --hash=sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c + # via nmsamplelocations +platformdirs==4.3.8 \ + --hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \ + --hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4 + # via virtualenv +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via + # nmsamplelocations + # pytest + # pytest-cov +pre-commit==4.3.0 \ + --hash=sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8 \ + --hash=sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16 + # via nmsamplelocations +propcache==0.3.2 \ + --hash=sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81 \ + --hash=sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6 \ + --hash=sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba \ + --hash=sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0 \ + --hash=sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168 \ + --hash=sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892 \ + --hash=sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1 \ + --hash=sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330 \ + --hash=sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44 \ + --hash=sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88 \ + --hash=sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3 \ + --hash=sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43 \ + --hash=sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4 \ + --hash=sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe \ + --hash=sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e \ + --hash=sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f \ + --hash=sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02 \ + --hash=sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e \ + --hash=sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1 \ + --hash=sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387 \ + --hash=sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198 \ + --hash=sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f \ + --hash=sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b \ + --hash=sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252 \ + --hash=sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c \ + --hash=sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770 \ + --hash=sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945 \ + --hash=sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33 \ + --hash=sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05 \ + --hash=sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28 \ + --hash=sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a \ + --hash=sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394 \ + --hash=sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725 \ + --hash=sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206 + # via + # aiohttp + # nmsamplelocations + # yarl +proto-plus==1.26.1 \ + --hash=sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66 \ + --hash=sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012 + # via + # google-api-core + # nmsamplelocations +protobuf==6.32.0 \ + --hash=sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1 \ + --hash=sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c \ + --hash=sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741 \ + --hash=sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2 \ + --hash=sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e \ + --hash=sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783 \ + --hash=sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0 + # via + # google-api-core + # googleapis-common-protos + # nmsamplelocations + # proto-plus +psutil==7.0.0 \ + --hash=sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25 \ + --hash=sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91 \ + --hash=sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da \ + --hash=sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34 \ + --hash=sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553 \ + --hash=sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456 \ + --hash=sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993 \ + --hash=sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99 + # via pact-python +psycopg-binary==3.2.9 \ + --hash=sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab \ + --hash=sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb \ + --hash=sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351 \ + --hash=sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2 \ + --hash=sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5 \ + --hash=sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9 \ + --hash=sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87 \ + --hash=sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e \ + --hash=sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f \ + --hash=sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748 \ + --hash=sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b + # via nmsamplelocations +psycopg2==2.9.10 \ + --hash=sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11 \ + --hash=sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2 + # via nmsamplelocations +pyasn1==0.6.1 \ + --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \ + --hash=sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034 + # via + # nmsamplelocations + # pyasn1-modules + # python-jose + # rsa +pyasn1-modules==0.4.2 \ + --hash=sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a \ + --hash=sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6 + # via + # google-auth + # nmsamplelocations +pycparser==2.22 \ + --hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \ + --hash=sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc + # via + # cffi + # nmsamplelocations +pydantic==2.11.7 \ + --hash=sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db \ + --hash=sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b + # via + # fastapi + # fastapi-pagination + # nmsamplelocations +pydantic-core==2.33.2 \ + --hash=sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56 \ + --hash=sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef \ + --hash=sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a \ + --hash=sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f \ + --hash=sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916 \ + --hash=sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a \ + --hash=sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849 \ + --hash=sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e \ + --hash=sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac \ + --hash=sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162 \ + --hash=sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc \ + --hash=sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5 \ + --hash=sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d \ + --hash=sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9 \ + --hash=sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9 \ + --hash=sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5 \ + --hash=sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9 \ + --hash=sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6 + # via + # nmsamplelocations + # pydantic +pygments==2.19.2 \ + --hash=sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887 \ + --hash=sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + # via + # nmsamplelocations + # pytest +pyjwt==2.10.1 \ + --hash=sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953 \ + --hash=sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb + # via nmsamplelocations +pyproj==3.7.2 \ + --hash=sha256:1914e29e27933ba6f9822663ee0600f169014a2859f851c054c88cf5ea8a333c \ + --hash=sha256:19466e529b1b15eeefdf8ff26b06fa745856c044f2f77bf0edbae94078c1dfa1 \ + --hash=sha256:237499c7862c578d0369e2b8ac56eec550e391a025ff70e2af8417139dabb41c \ + --hash=sha256:25b0b7cb0042444c29a164b993c45c1b8013d6c48baa61dc1160d834a277e83b \ + --hash=sha256:2aaa328605ace41db050d06bac1adc11f01b71fe95c18661497763116c3a0f02 \ + --hash=sha256:2b617d573be4118c11cd96b8891a0b7f65778fa7733ed8ecdb297a447d439100 \ + --hash=sha256:2da731876d27639ff9d2d81c151f6ab90a1546455fabd93368e753047be344a2 \ + --hash=sha256:35dccbce8201313c596a970fde90e33605248b66272595c061b511c8100ccc08 \ + --hash=sha256:39a0cf1ecc7e282d1d30f36594ebd55c9fae1fda8a2622cee5d100430628f88c \ + --hash=sha256:47d87db2d2c436c5fd0409b34d70bb6cdb875cca2ebe7a9d1c442367b0ab8d59 \ + --hash=sha256:5141a538ffdbe4bfd157421828bb2e07123a90a7a2d6f30fa1462abcfb5ce681 \ + --hash=sha256:55a3610d75023c7b1c6e583e48ef8f62918e85a2ae81300569d9f104d6684bb6 \ + --hash=sha256:5a964da1696b8522806f4276ab04ccfff8f9eb95133a92a25900697609d40112 \ + --hash=sha256:5aff3343038d7426aa5076f07feb88065f50e0502d1b0d7c22ddfdd2c75a3f81 \ + --hash=sha256:77f066626030f41be543274f5ac79f2a511fe89860ecd0914f22131b40a0ec25 \ + --hash=sha256:8115faf2597f281a42ab608ceac346b4eb1383d3b45ab474fd37341c4bf82a67 \ + --hash=sha256:85def3a6388e9ba51f964619aa002a9d2098e77c6454ff47773bb68871024281 \ + --hash=sha256:8c225f5978abd506fd9a78eaaf794435e823c9156091cabaab5374efb29d7f69 \ + --hash=sha256:8d7349182fa622696787cc9e195508d2a41a64765da9b8a6bee846702b9e6220 \ + --hash=sha256:b0552178c61f2ac1c820d087e8ba6e62b29442debddbb09d51c4bf8acc84d888 \ + --hash=sha256:b1bccefec3875ab81eabf49059e2b2ea77362c178b66fd3528c3e4df242f1516 \ + --hash=sha256:b7544e0a3d6339dc9151e9c8f3ea62a936ab7cc446a806ec448bbe86aebb979b \ + --hash=sha256:bb41c29d5f60854b1075853fe80c58950b398d4ebb404eb532536ac8d2834ed7 \ + --hash=sha256:bbbac2f930c6d266f70ec75df35ef851d96fdb3701c674f42fd23a9314573b37 \ + --hash=sha256:c79b9b84c4a626c5dc324c0d666be0bfcebd99f7538d66e8898c2444221b3da7 \ + --hash=sha256:c9b6f1d8ad3e80a0ee0903a778b6ece7dca1d1d40f6d114ae01bc8ddbad971aa \ + --hash=sha256:ceecf374cacca317bc09e165db38ac548ee3cad07c3609442bd70311c59c21aa \ + --hash=sha256:d230b186eb876ed4f29a7c5ee310144c3a0e44e89e55f65fb3607e13f6db337c \ + --hash=sha256:d27b48f0e81beeaa2b4d60c516c3a1cfbb0c7ff6ef71256d8e9c07792f735279 \ + --hash=sha256:d5371ca114d6990b675247355a801925814eca53e6c4b2f1b5c0a956336ee36e \ + --hash=sha256:d9d25bae416a24397e0d85739f84d323b55f6511e45a522dd7d7eae70d10c7e4 \ + --hash=sha256:e258ab4dbd3cf627809067c0ba8f9884ea76c8e5999d039fb37a1619c6c3e1f6 \ + --hash=sha256:f000841e98ea99acbb7b8ca168d67773b0191de95187228a16110245c5d954d5 \ + --hash=sha256:f18c0579dd6be00b970cb1a6719197fceecc407515bab37da0066f0184aafdf3 \ + --hash=sha256:f54d91ae18dd23b6c0ab48126d446820e725419da10617d86a1b69ada6d881d3 \ + --hash=sha256:f7f5133dca4c703e8acadf6f30bc567d39a42c6af321e7f81975c2518f3ed357 \ + --hash=sha256:fc52ba896cfc3214dc9f9ca3c0677a623e8fdd096b257c14a31e719d21ff3fdd + # via nmsamplelocations +pyshp==2.3.1 \ + --hash=sha256:4caec82fd8dd096feba8217858068bacb2a3b5950f43c048c6dc32a3489d5af1 \ + --hash=sha256:67024c0ccdc352ba5db777c4e968483782dfa78f8e200672a90d2d30fd8b7b49 + # via nmsamplelocations +pytest==8.4.1 \ + --hash=sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7 \ + --hash=sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c + # via + # nmsamplelocations + # pytest-cov +pytest-cov==6.2.1 \ + --hash=sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2 \ + --hash=sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5 + # via nmsamplelocations +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via + # nmsamplelocations + # pandas + # pg8000 +python-dotenv==1.1.1 \ + --hash=sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc \ + --hash=sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab + # via dotenv +python-jose==3.5.0 \ + --hash=sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771 \ + --hash=sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b + # via nmsamplelocations +python-multipart==0.0.20 \ + --hash=sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104 \ + --hash=sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13 + # via nmsamplelocations +pytz==2025.2 \ + --hash=sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3 \ + --hash=sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00 + # via + # nmsamplelocations + # pandas +pyyaml==6.0.2 \ + --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ + --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ + --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ + --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ + --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ + --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ + --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ + --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ + --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ + --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba + # via pre-commit +requests==2.32.5 \ + --hash=sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 \ + --hash=sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf + # via + # cloud-sql-python-connector + # google-api-core + # google-cloud-storage + # nmsamplelocations + # pact-python +rsa==4.9.1 \ + --hash=sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762 \ + --hash=sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75 + # via + # google-auth + # nmsamplelocations + # python-jose +scramp==1.4.6 \ + --hash=sha256:a0cf9d2b4624b69bac5432dd69fecfc55a542384fe73c3a23ed9b138cda484e1 \ + --hash=sha256:fe055ebbebf4397b9cb323fcc4b299f219cd1b03fd673ca40c97db04ac7d107e + # via + # nmsamplelocations + # pg8000 +sentry-sdk==2.35.0 \ + --hash=sha256:5ea58d352779ce45d17bc2fa71ec7185205295b83a9dbb5707273deb64720092 \ + --hash=sha256:6e0c29b9a5d34de8575ffb04d289a987ff3053cf2c98ede445bea995e3830263 + # via nmsamplelocations +shapely==2.1.1 \ + --hash=sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de \ + --hash=sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d \ + --hash=sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6 \ + --hash=sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c \ + --hash=sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48 \ + --hash=sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05 \ + --hash=sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772 \ + --hash=sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9 \ + --hash=sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8 \ + --hash=sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7 \ + --hash=sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97 \ + --hash=sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d \ + --hash=sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913 \ + --hash=sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a \ + --hash=sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db \ + --hash=sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0 \ + --hash=sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52 + # via nmsamplelocations +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via + # ecdsa + # nmsamplelocations + # pact-python + # python-dateutil +sniffio==1.3.1 \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc + # via + # anyio + # nmsamplelocations +sqlalchemy==2.0.43 \ + --hash=sha256:14111d22c29efad445cd5021a70a8b42f7d9152d8ba7f73304c4d82460946aaa \ + --hash=sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc \ + --hash=sha256:21b27b56eb2f82653168cefe6cb8e970cdaf4f3a6cb2c5e3c3c1cf3158968ff9 \ + --hash=sha256:5d79f9fdc9584ec83d1b3c75e9f4595c49017f5594fee1a2217117647225d738 \ + --hash=sha256:788bfcef6787a7764169cfe9859fe425bf44559619e1d9f56f5bddf2ebf6f417 \ + --hash=sha256:7f1ac7828857fcedb0361b48b9ac4821469f7694089d15550bbcf9ab22564a1d \ + --hash=sha256:971ba928fcde01869361f504fcff3b7143b47d30de188b11c6357c0505824197 \ + --hash=sha256:9c5a9da957c56e43d72126a3f5845603da00e0293720b03bde0aacffcf2dc04f \ + --hash=sha256:9df7126fd9db49e3a5a3999442cc67e9ee8971f3cb9644250107d7296cb2a164 \ + --hash=sha256:e7c08f57f75a2bb62d7ee80a89686a5e5669f199235c6d1dac75cd59374091c3 + # via + # alembic + # geoalchemy2 + # nmsamplelocations + # sqlalchemy-continuum + # sqlalchemy-searchable + # sqlalchemy-utils +sqlalchemy-continuum==1.4.2 \ + --hash=sha256:0fd2be79f718eda47c2206879d92ec4ebf1889364637b3caf3ee5d34bd19c8e3 \ + --hash=sha256:154588d79deb8b1683b5f39c130e6f0ad793c0b2f27e8c210565c23fb6fe74de + # via nmsamplelocations +sqlalchemy-searchable==2.1.0 \ + --hash=sha256:89d120ed1a752d22e32b3f028f62cae571241ccce081df8d8a42e1fa9a53da93 \ + --hash=sha256:a4ef31d6ba60face514563beed6c4a72b5639add67503689e83d5f7d9a6c76ec + # via nmsamplelocations +sqlalchemy-utils==0.41.2 \ + --hash=sha256:85cf3842da2bf060760f955f8467b87983fb2e30f1764fd0e24a48307dc8ec6e \ + --hash=sha256:bc599c8c3b3319e53ce6c5c3c471120bd325d0071fb6f38a10e924e3d07b9990 + # via + # nmsamplelocations + # sqlalchemy-continuum + # sqlalchemy-searchable +starlette==0.47.3 \ + --hash=sha256:6bc94f839cc176c4858894f1f8908f0ab79dfec1a6b8402f6da9be26ebea52e9 \ + --hash=sha256:89c0778ca62a76b826101e7c709e70680a1699ca7da6b44d38eb0a7e61fe4b51 + # via + # fastapi + # nmsamplelocations +types-pytz==2025.2.0.20250809 \ + --hash=sha256:222e32e6a29bb28871f8834e8785e3801f2dc4441c715cd2082b271eecbe21e5 \ + --hash=sha256:4f55ed1b43e925cf851a756fe1707e0f5deeb1976e15bf844bcaa025e8fbd0db + # via pandas-stubs +typing-extensions==4.14.1 \ + --hash=sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36 \ + --hash=sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76 + # via + # aiosqlite + # alembic + # fastapi + # fastapi-pagination + # nmsamplelocations + # pydantic + # pydantic-core + # sqlalchemy + # typing-inspection +typing-inspection==0.4.1 \ + --hash=sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51 \ + --hash=sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28 + # via + # nmsamplelocations + # pydantic +tzdata==2025.2 \ + --hash=sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8 \ + --hash=sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9 + # via + # nmsamplelocations + # pandas +urllib3==2.5.0 \ + --hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \ + --hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc + # via + # nmsamplelocations + # requests + # sentry-sdk +uvicorn==0.35.0 \ + --hash=sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a \ + --hash=sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01 + # via + # nmsamplelocations + # pact-python +virtualenv==20.32.0 \ + --hash=sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56 \ + --hash=sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0 + # via pre-commit +yarl==1.20.1 \ + --hash=sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53 \ + --hash=sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a \ + --hash=sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02 \ + --hash=sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3 \ + --hash=sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04 \ + --hash=sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458 \ + --hash=sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc \ + --hash=sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d \ + --hash=sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7 \ + --hash=sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c \ + --hash=sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691 \ + --hash=sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f \ + --hash=sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3 \ + --hash=sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28 \ + --hash=sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513 \ + --hash=sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31 \ + --hash=sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16 \ + --hash=sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3 \ + --hash=sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf \ + --hash=sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1 \ + --hash=sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f \ + --hash=sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77 \ + --hash=sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e \ + --hash=sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c \ + --hash=sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1 \ + --hash=sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b \ + --hash=sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d \ + --hash=sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390 \ + --hash=sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be \ + --hash=sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac \ + --hash=sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5 \ + --hash=sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4 \ + --hash=sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653 \ + --hash=sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d \ + --hash=sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7 \ + --hash=sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce + # via + # aiohttp + # nmsamplelocations + # pact-python From b19e53f9b38132b10c31fc655686cb0052272736 Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 15:02:38 -0600 Subject: [PATCH 23/46] feat: update psycopg dependencies to use psycopg2-binary and remove pact-python --- pyproject.toml | 4 +-- requirements.txt | 59 ++++++++-------------------------- uv.lock | 84 +++++++++--------------------------------------- 3 files changed, 31 insertions(+), 116 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d56ea3af6..63b6e4c9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,6 @@ dependencies = [ "multidict==6.6.3", "numpy==2.3.2", "packaging==25.0", - "pact-python>=2.3.3", "pandas==2.3.1", "pandas-stubs==2.3.0.250703", "pg8000==1.31.4", @@ -65,8 +64,7 @@ dependencies = [ "propcache==0.3.2", "proto-plus==1.26.1", "protobuf==6.32.0", - "psycopg-binary>=3.2.9", - "psycopg2==2.9.10", + "psycopg2-binary>=2.9.10", "pyasn1==0.6.1", "pyasn1-modules==0.4.2", "pycparser==2.22", diff --git a/requirements.txt b/requirements.txt index 64ecfd934..017f04110 100644 --- a/requirements.txt +++ b/requirements.txt @@ -169,7 +169,6 @@ cffi==1.17.1 \ # via # cryptography # nmsamplelocations - # pact-python cfgv==3.4.0 \ --hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \ --hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560 @@ -207,7 +206,6 @@ click==8.2.1 \ --hash=sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b # via # nmsamplelocations - # pact-python # uvicorn cloud-sql-python-connector==1.18.4 \ --hash=sha256:0a77a16ab2d93fc78d8593175cb69fedfbc1c67aa99f9b3ba70b5026343db092 \ @@ -325,7 +323,6 @@ fastapi==0.116.1 \ # via # fastapi-pagination # nmsamplelocations - # pact-python # sentry-sdk fastapi-pagination==0.14.0 \ --hash=sha256:72ed471c79c3aa07940b287b9cf022503ec2e69e1f2ca5a7a5fcd8152cc43392 \ @@ -639,16 +636,6 @@ packaging==25.0 \ # gunicorn # nmsamplelocations # pytest -pact-python==2.3.3 \ - --hash=sha256:11433275e3b6d7eea8b0d328d3711a5c458575f69f50815027860a291d48f62f \ - --hash=sha256:5bfda6100b0a146c8238a0c822cab918936192e2ea4b03e48da4bedb93756510 \ - --hash=sha256:7c382743eb89a18b10d0b387a022fed1ec0cce7881bbddd409844e6e172af9e8 \ - --hash=sha256:c41ff6911cc054b11c79c1e7524f30ff676ee1aaa85dca1a4dff0e83ca98d2bd \ - --hash=sha256:c696cad249b23c8bf926664dd294b0bd6e5e7fd910dcc002f96998f1b6a37a8e \ - --hash=sha256:c6e4ef70894db51d876fb765bc97eb9118b8da880e4fc3a2eef11eb9977919ec \ - --hash=sha256:ee83357872fca5cc7ab07ff53ffc88065557787663188154f849c4edaa838ba4 \ - --hash=sha256:f939e673b0268d90c13aa6a6ab18b7a32dd44a7a6587de64a5f95a25f690293f - # via nmsamplelocations pandas==2.3.1 \ --hash=sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2 \ --hash=sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956 \ @@ -800,32 +787,19 @@ protobuf==6.32.0 \ # googleapis-common-protos # nmsamplelocations # proto-plus -psutil==7.0.0 \ - --hash=sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25 \ - --hash=sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91 \ - --hash=sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da \ - --hash=sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34 \ - --hash=sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553 \ - --hash=sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456 \ - --hash=sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993 \ - --hash=sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99 - # via pact-python -psycopg-binary==3.2.9 \ - --hash=sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab \ - --hash=sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb \ - --hash=sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351 \ - --hash=sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2 \ - --hash=sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5 \ - --hash=sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9 \ - --hash=sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87 \ - --hash=sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e \ - --hash=sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f \ - --hash=sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748 \ - --hash=sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b - # via nmsamplelocations -psycopg2==2.9.10 \ - --hash=sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11 \ - --hash=sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2 +psycopg2-binary==2.9.10 \ + --hash=sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f \ + --hash=sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7 \ + --hash=sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d \ + --hash=sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142 \ + --hash=sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73 \ + --hash=sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d \ + --hash=sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2 \ + --hash=sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673 \ + --hash=sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909 \ + --hash=sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb \ + --hash=sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1 \ + --hash=sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567 # via nmsamplelocations pyasn1==0.6.1 \ --hash=sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629 \ @@ -984,7 +958,6 @@ requests==2.32.5 \ # google-api-core # google-cloud-storage # nmsamplelocations - # pact-python rsa==4.9.1 \ --hash=sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762 \ --hash=sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75 @@ -1027,7 +1000,6 @@ six==1.17.0 \ # via # ecdsa # nmsamplelocations - # pact-python # python-dateutil sniffio==1.3.1 \ --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 \ @@ -1113,9 +1085,7 @@ urllib3==2.5.0 \ uvicorn==0.35.0 \ --hash=sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a \ --hash=sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01 - # via - # nmsamplelocations - # pact-python + # via nmsamplelocations virtualenv==20.32.0 \ --hash=sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56 \ --hash=sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0 @@ -1160,4 +1130,3 @@ yarl==1.20.1 \ # via # aiohttp # nmsamplelocations - # pact-python diff --git a/uv.lock b/uv.lock index 562872de4..ca7ab0709 100644 --- a/uv.lock +++ b/uv.lock @@ -931,7 +931,6 @@ dependencies = [ { name = "multidict" }, { name = "numpy" }, { name = "packaging" }, - { name = "pact-python" }, { name = "pandas" }, { name = "pandas-stubs" }, { name = "pg8000" }, @@ -942,8 +941,7 @@ dependencies = [ { name = "propcache" }, { name = "proto-plus" }, { name = "protobuf" }, - { name = "psycopg-binary" }, - { name = "psycopg2" }, + { name = "psycopg2-binary" }, { name = "pyasn1" }, { name = "pyasn1-modules" }, { name = "pycparser" }, @@ -1036,7 +1034,6 @@ requires-dist = [ { name = "multidict", specifier = "==6.6.3" }, { name = "numpy", specifier = "==2.3.2" }, { name = "packaging", specifier = "==25.0" }, - { name = "pact-python", specifier = ">=2.3.3" }, { name = "pandas", specifier = "==2.3.1" }, { name = "pandas-stubs", specifier = "==2.3.0.250703" }, { name = "pg8000", specifier = "==1.31.4" }, @@ -1047,8 +1044,7 @@ requires-dist = [ { name = "propcache", specifier = "==0.3.2" }, { name = "proto-plus", specifier = "==1.26.1" }, { name = "protobuf", specifier = "==6.32.0" }, - { name = "psycopg-binary", specifier = ">=3.2.9" }, - { name = "psycopg2", specifier = "==2.9.10" }, + { name = "psycopg2-binary", specifier = ">=2.9.10" }, { name = "pyasn1", specifier = "==0.6.1" }, { name = "pyasn1-modules", specifier = "==0.4.2" }, { name = "pycparser", specifier = "==2.22" }, @@ -1160,31 +1156,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] -[[package]] -name = "pact-python" -version = "2.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, - { name = "click" }, - { name = "fastapi" }, - { name = "psutil" }, - { name = "requests" }, - { name = "six" }, - { name = "uvicorn" }, - { name = "yarl" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/ca/de5ce2f6ad2a777696f632cef39501cf6eba070bb56df42f1b3f3acf755f/pact_python-2.3.3.tar.gz", hash = "sha256:c6e4ef70894db51d876fb765bc97eb9118b8da880e4fc3a2eef11eb9977919ec", size = 152378, upload-time = "2025-07-17T11:41:02.851Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/a8/7a8d5034d9ac9f93aaab1830fc15a0ec6f84a5b1fac2c58011a9090bab6d/pact_python-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f939e673b0268d90c13aa6a6ab18b7a32dd44a7a6587de64a5f95a25f690293f", size = 30508131, upload-time = "2025-07-17T11:40:19.53Z" }, - { url = "https://files.pythonhosted.org/packages/22/42/fba3d6f8b2d5440d3e47518ae24b606747f640cc5632c0e94f933da53066/pact_python-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ee83357872fca5cc7ab07ff53ffc88065557787663188154f849c4edaa838ba4", size = 31478250, upload-time = "2025-07-17T11:40:22.991Z" }, - { url = "https://files.pythonhosted.org/packages/41/12/41ee486adb75876aca30ce3931c85d201443c36ae9b9aa4aa46221849106/pact_python-2.3.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11433275e3b6d7eea8b0d328d3711a5c458575f69f50815027860a291d48f62f", size = 33945574, upload-time = "2025-07-17T11:40:26.27Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1a/0a803dfc09a60354d8d870ad7cf99a7ad5b6950a29873a542d9e4501fbbc/pact_python-2.3.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5bfda6100b0a146c8238a0c822cab918936192e2ea4b03e48da4bedb93756510", size = 32805592, upload-time = "2025-07-17T11:40:30.025Z" }, - { url = "https://files.pythonhosted.org/packages/10/d8/fd95a3dd8cbf3e8ce654e3aa17982f6bcf9e19aa60f184c2be26ad47df0d/pact_python-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7c382743eb89a18b10d0b387a022fed1ec0cce7881bbddd409844e6e172af9e8", size = 15265439, upload-time = "2025-07-17T11:40:33.437Z" }, - { url = "https://files.pythonhosted.org/packages/94/66/287ad59f1abccb51b4bcdd457d74605579b8f6de1b088908332727830572/pact_python-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c696cad249b23c8bf926664dd294b0bd6e5e7fd910dcc002f96998f1b6a37a8e", size = 14242800, upload-time = "2025-07-17T11:40:35.881Z" }, - { url = "https://files.pythonhosted.org/packages/80/7d/1f10f77b1162808ee91a7266bc0881ecf929abdd7592d8c217a15ff6383d/pact_python-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:c41ff6911cc054b11c79c1e7524f30ff676ee1aaa85dca1a4dff0e83ca98d2bd", size = 22455236, upload-time = "2025-07-17T11:40:38.99Z" }, -] - [[package]] name = "pandas" version = "2.3.1" @@ -1404,45 +1375,22 @@ wheels = [ ] [[package]] -name = "psutil" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, - { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, - { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, - { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, -] - -[[package]] -name = "psycopg-binary" -version = "3.2.9" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/0b/f61ff4e9f23396aca674ed4d5c9a5b7323738021d5d72d36d8b865b3deaf/psycopg_binary-3.2.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:98bbe35b5ad24a782c7bf267596638d78aa0e87abc7837bdac5b2a2ab954179e", size = 4017127, upload-time = "2025-05-13T16:08:21.391Z" }, - { url = "https://files.pythonhosted.org/packages/bc/00/7e181fb1179fbfc24493738b61efd0453d4b70a0c4b12728e2b82db355fd/psycopg_binary-3.2.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:72691a1615ebb42da8b636c5ca9f2b71f266be9e172f66209a361c175b7842c5", size = 4080322, upload-time = "2025-05-13T16:08:24.049Z" }, - { url = "https://files.pythonhosted.org/packages/58/fd/94fc267c1d1392c4211e54ccb943be96ea4032e761573cf1047951887494/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25ab464bfba8c401f5536d5aa95f0ca1dd8257b5202eede04019b4415f491351", size = 4655097, upload-time = "2025-05-13T16:08:27.376Z" }, - { url = "https://files.pythonhosted.org/packages/41/17/31b3acf43de0b2ba83eac5878ff0dea5a608ca2a5c5dd48067999503a9de/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e8aeefebe752f46e3c4b769e53f1d4ad71208fe1150975ef7662c22cca80fab", size = 4482114, upload-time = "2025-05-13T16:08:30.781Z" }, - { url = "https://files.pythonhosted.org/packages/85/78/b4d75e5fd5a85e17f2beb977abbba3389d11a4536b116205846b0e1cf744/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7e4e4dd177a8665c9ce86bc9caae2ab3aa9360b7ce7ec01827ea1baea9ff748", size = 4737693, upload-time = "2025-05-13T16:08:34.625Z" }, - { url = "https://files.pythonhosted.org/packages/3b/95/7325a8550e3388b00b5e54f4ced5e7346b531eb4573bf054c3dbbfdc14fe/psycopg_binary-3.2.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc2915949e5c1ea27a851f7a472a7da7d0a40d679f0a31e42f1022f3c562e87", size = 4437423, upload-time = "2025-05-13T16:08:37.444Z" }, - { url = "https://files.pythonhosted.org/packages/1a/db/cef77d08e59910d483df4ee6da8af51c03bb597f500f1fe818f0f3b925d3/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1fa38a4687b14f517f049477178093c39c2a10fdcced21116f47c017516498f", size = 3758667, upload-time = "2025-05-13T16:08:40.116Z" }, - { url = "https://files.pythonhosted.org/packages/95/3e/252fcbffb47189aa84d723b54682e1bb6d05c8875fa50ce1ada914ae6e28/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5be8292d07a3ab828dc95b5ee6b69ca0a5b2e579a577b39671f4f5b47116dfd2", size = 3320576, upload-time = "2025-05-13T16:08:43.243Z" }, - { url = "https://files.pythonhosted.org/packages/1c/cd/9b5583936515d085a1bec32b45289ceb53b80d9ce1cea0fef4c782dc41a7/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:778588ca9897b6c6bab39b0d3034efff4c5438f5e3bd52fda3914175498202f9", size = 3411439, upload-time = "2025-05-13T16:08:47.321Z" }, - { url = "https://files.pythonhosted.org/packages/45/6b/6f1164ea1634c87956cdb6db759e0b8c5827f989ee3cdff0f5c70e8331f2/psycopg_binary-3.2.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f0d5b3af045a187aedbd7ed5fc513bd933a97aaff78e61c3745b330792c4345b", size = 3477477, upload-time = "2025-05-13T16:08:51.166Z" }, - { url = "https://files.pythonhosted.org/packages/7b/1d/bf54cfec79377929da600c16114f0da77a5f1670f45e0c3af9fcd36879bc/psycopg_binary-3.2.9-cp313-cp313-win_amd64.whl", hash = "sha256:2290bc146a1b6a9730350f695e8b670e1d1feb8446597bed0bbe7c3c30e0abcb", size = 2928009, upload-time = "2025-05-13T16:08:53.67Z" }, -] - -[[package]] -name = "psycopg2" +name = "psycopg2-binary" version = "2.9.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/62/51/2007ea29e605957a17ac6357115d0c1a1b60c8c984951c19419b3474cdfd/psycopg2-2.9.10.tar.gz", hash = "sha256:12ec0b40b0273f95296233e8750441339298e6a572f7039da5b260e3c8b60e11", size = 385672, upload-time = "2024-10-16T11:24:54.832Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/49/a6cfc94a9c483b1fa401fbcb23aca7892f60c7269c5ffa2ac408364f80dc/psycopg2-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:91fd603a2155da8d0cfcdbf8ab24a2d54bca72795b90d2a3ed2b6da8d979dee2", size = 2569060, upload-time = "2025-01-04T20:09:15.28Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764, upload-time = "2024-10-16T11:24:58.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699, upload-time = "2024-10-16T11:21:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245, upload-time = "2024-10-16T11:21:51.989Z" }, + { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631, upload-time = "2024-10-16T11:21:57.584Z" }, + { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140, upload-time = "2024-10-16T11:22:02.005Z" }, + { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762, upload-time = "2024-10-16T11:22:06.412Z" }, + { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967, upload-time = "2024-10-16T11:22:11.583Z" }, + { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326, upload-time = "2024-10-16T11:22:16.406Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712, upload-time = "2024-10-16T11:22:21.366Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155, upload-time = "2024-10-16T11:22:25.684Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356, upload-time = "2024-10-16T11:22:30.562Z" }, + { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224, upload-time = "2025-01-04T20:09:19.234Z" }, ] [[package]] From 986e6b2be8ec29aec4c1eaeebfce8a6a563306c0 Mon Sep 17 00:00:00 2001 From: jirhiker Date: Fri, 5 Sep 2025 01:27:44 +0000 Subject: [PATCH 24/46] Formatting changes --- transfers/contact_transfer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transfers/contact_transfer.py b/transfers/contact_transfer.py index 8f25dba6b..65cb6a641 100644 --- a/transfers/contact_transfer.py +++ b/transfers/contact_transfer.py @@ -131,7 +131,7 @@ def _iterate(session, row): contact2 = Contact( name=f"{row.SecondFirstName} {row.SecondLastName}", role="Owner", # TODO: role needs to be extracted from somewhere - contact_type='Secondary', + contact_type="Secondary", # TODO: needs to be implemented # priority=2, organization=row.Company, # Assumes organization applies to both contacts From 7f06627655fcaebea37f249f8541c52854ff7b59 Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 19:47:40 -0600 Subject: [PATCH 25/46] feat: update Procfile to use transfers.entrypoint for web server feat: add set_secrets.sh to .gitignore --- .gitignore | 1 + Procfile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1f1637d10..e5541f275 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ migrate.sh launcher.sh gcs_credentials.json transfers/data/assets* +set_secrets.sh # deployment files app.yaml \ No newline at end of file diff --git a/Procfile b/Procfile index 8e59db9b4..82b4db951 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: gunicorn -b :8080 entrypoint:app -k uvicorn.workers.UvicornWorker +web: gunicorn -b :8080 transfers.entrypoint:app -k uvicorn.workers.UvicornWorker From 14a99616772bf968edccf38945601fbed3980198 Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 4 Sep 2025 21:26:54 -0600 Subject: [PATCH 26/46] feat: enhance contact transfer logic and reduce batch limit --- .gitignore | 1 - transfers/contact_transfer.py | 293 ++++++++++++++++++---------------- transfers/transfer.py | 32 ++-- 3 files changed, 170 insertions(+), 156 deletions(-) diff --git a/.gitignore b/.gitignore index e5541f275..1f1637d10 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ migrate.sh launcher.sh gcs_credentials.json transfers/data/assets* -set_secrets.sh # deployment files app.yaml \ No newline at end of file diff --git a/transfers/contact_transfer.py b/transfers/contact_transfer.py index cc5d0612b..61da8e325 100644 --- a/transfers/contact_transfer.py +++ b/transfers/contact_transfer.py @@ -58,157 +58,27 @@ def transfer_contacts(session): odf = filter_to_valid_point_ids(session, odf) for i, row in odf.iterrows(): thing = session.query(Thing).where(Thing.name == row.PointID).first() + print(f"Processing PointID: {i} {row.PointID}") if thing is None: print(f"Thing with PointID {row.PointID} not found. Skipping owner.") continue - # TODO: extract role from OwnerComment - # role = extract_owner_role(row.OwnerComment) - role = "Owner" - release_status = "private" - - # TODO: put in guards for null values try: - - if row.FirstName is None and row.LastName is None: - name = None - elif row.FirstName is not None and row.LastName is None: - name = row.FirstName - elif row.FirstName is None and row.LastName is not None: - name = row.LastName - else: - name = f"{row.FirstName} {row.LastName}" - - first_contact_data = { - "thing_id": thing.id, - "release_status": release_status, - "name": name, - "role": role, - "contact_type": "Primary", - "organization": row.Company, - "nma_pk_owners": row.OwnerKey, - } - - CreateContact.model_validate(first_contact_data) - - first_contact_data.pop("thing_id") - first_contact = Contact(**first_contact_data) - - assoc = ThingContactAssociation() - assoc.thing = thing - assoc.contact = first_contact - - if row.Email: - first_contact.emails.append( - Email( - email=row.Email, - email_type="Primary", - release_status=release_status, - ) - ) - if row.Phone: - first_contact.phones.append( - Phone( - phone_number=row.Phone, - phone_type="Primary", - release_status=release_status, - ) - ) - if row.CellPhone: - first_contact.phones.append( - Phone( - phone_number=row.CellPhone, - phone_type="Mobile", - release_status=release_status, - ) - ) - - if row.MailingAddress: - first_contact.addresses.append( - Address( - address_line_1=row.MailingAddress, - city=row.MailCity, - state=row.MailState, - postal_code=row.MailZipCode, - address_type="Mailing", - release_status=release_status, - ) - ) - - first_contact.addresses.append( - Address( - address_line_1=row.PhysicalAddress, - city=row.PhysicalCity, - state=row.PhysicalState, - postal_code=row.PhysicalZipCode, - address_type="Physical", - release_status=release_status, - ) - ) - - session.add(assoc) - session.add(first_contact) + add_first_contact(session, row, thing) session.commit() - + session.flush() + print(f'added first contact for PointID {row.PointID}') except Exception as e: print( - f"Skipping first contact for PointID {row.PointID} due to validation error: {e}" + f"Skipping second contact for PointID {row.PointID} due to validation error: {e}" ) - from pprint import pprint - - pprint(e) session.rollback() try: - if row.SecondFirstName is None and row.SecondLastName is None: - name = None - elif row.SecondFirstName is not None and row.SecondLastName is None: - name = row.SecondFirstName - elif row.SecondFirstName is None and row.SecondLastName is not None: - name = row.SecondLastName - else: - name = f"{row.SecondFirstName} {row.SecondLastName}" - - second_contact_data = { - "thing_id": thing.id, - "release_status": release_status, - "name": name, - "role": "Owner", - "contact_type": "Secondary", - "organization": row.Company, - "nma_pk_owners": row.OwnerKey, - } - - CreateContact.model_validate(second_contact_data) - - second_contact_data.pop("thing_id") - second_contact = Contact(**second_contact_data) - - assoc = ThingContactAssociation() - assoc.thing = thing - assoc.contact = second_contact - - if row.SecondCtctEmail: - second_contact.emails.append( - Email( - email=row.SecondCtctEmail, - email_type="Primary", - release_status=release_status, - ) - ) - - if row.SecondCtctPhone: - second_contact.phones.append( - Phone( - phone_number=row.SecondCtctPhone, - phone_type="Primary", - release_status=release_status, - ) - ) - - session.add(assoc) - session.add(second_contact) - + add_second_contact(session, row, thing) + session.commit() + session.flush() + print(f'added second contact for PointID {row.PointID}') except Exception as e: print( f"Skipping second contact for PointID {row.PointID} due to validation error: {e}" @@ -216,4 +86,149 @@ def transfer_contacts(session): session.rollback() +def add_first_contact(session, row, thing): + # TODO: extract role from OwnerComment + # role = extract_owner_role(row.OwnerComment) + role = "Owner" + release_status = "private" + + # TODO: put in guards for null values + if row.FirstName is None and row.LastName is None: + name = None + elif row.FirstName is not None and row.LastName is None: + name = row.FirstName + elif row.FirstName is None and row.LastName is not None: + name = row.LastName + else: + name = f"{row.FirstName} {row.LastName}" + + contact_data = { + "thing_id": thing.id, + "release_status": release_status, + "name": name, + "role": role, + "contact_type": "Primary", + "organization": row.Company, + "nma_pk_owners": row.OwnerKey, + } + + contact = _make_contact_and_assoc(session, contact_data, thing) + + if row.Email: + # TODO: use Pydantic to validate email + contact.emails.append( + Email( + email=row.Email, + email_type="Primary", + release_status=release_status, + ) + ) + if row.Phone: + # TODO: use Pydantic to validate phone + contact.phones.append( + Phone( + phone_number=row.Phone, + phone_type="Primary", + release_status=release_status, + ) + ) + if row.CellPhone: + # TODO: use Pydantic to validate cell phone + contact.phones.append( + Phone( + phone_number=row.CellPhone, + phone_type="Mobile", + release_status=release_status, + ) + ) + + if row.MailingAddress: + # TODO: use Pydantic to validate MailingAddress + contact.addresses.append( + Address( + address_line_1=row.MailingAddress, + city=row.MailCity, + state=row.MailState, + postal_code=row.MailZipCode, + address_type="Mailing", + release_status=release_status, + ) + ) + + contact.addresses.append( + # TODO: use Pydantic to validate PhysicalAddress + Address( + address_line_1=row.PhysicalAddress, + city=row.PhysicalCity, + state=row.PhysicalState, + postal_code=row.PhysicalZipCode, + address_type="Physical", + release_status=release_status, + ) + ) + + +def add_second_contact(session, row, thing): + release_status = "private" + if row.SecondFirstName is None and row.SecondLastName is None: + name = None + elif row.SecondFirstName is not None and row.SecondLastName is None: + name = row.SecondFirstName + elif row.SecondFirstName is None and row.SecondLastName is not None: + name = row.SecondLastName + else: + name = f"{row.SecondFirstName} {row.SecondLastName}" + + contact_data = { + "thing_id": thing.id, + "release_status": release_status, + "name": name, + "role": "Owner", + "contact_type": "Secondary", + "organization": row.Company, + "nma_pk_owners": row.OwnerKey, + } + + # CreateContact.model_validate(second_contact_data) + # + # second_contact_data.pop("thing_id") + # second_contact = Contact(**second_contact_data) + # + # assoc = ThingContactAssociation() + # assoc.thing = thing + # assoc.contact = second_contact + contact = _make_contact_and_assoc(session, contact_data, thing) + + if row.SecondCtctEmail: + contact.emails.append( + Email( + email=row.SecondCtctEmail, + email_type="Primary", + release_status=release_status, + ) + ) + + if row.SecondCtctPhone: + contact.phones.append( + Phone( + phone_number=row.SecondCtctPhone, + phone_type="Primary", + release_status=release_status, + ) + ) + + + +def _make_contact_and_assoc(session, data, thing): + CreateContact.model_validate(data) + + data.pop("thing_id") + contact = Contact(**data) + + assoc = ThingContactAssociation() + assoc.thing = thing + assoc.contact = contact + session.add(assoc) + session.add(contact) + return contact # ============= EOF ============================================= diff --git a/transfers/transfer.py b/transfers/transfer.py index 8f9f9319d..a8baad28e 100644 --- a/transfers/transfer.py +++ b/transfers/transfer.py @@ -66,7 +66,7 @@ def main_transfer(): cleanup_wells_flag = False - limit = 1000 + limit = 500 with session_ctx() as sess: if init: @@ -77,21 +77,21 @@ def main_transfer(): transfer_wells(sess, limit) transfer_wellscreens(sess) # - # if init or transfer_spring_flag: - # message("TRANSFERRING SPRINGS") - # transfer_springs(sess, limit) - # - # if init or transfer_perennial_stream_flag: - # message("TRANSFERRING PERENNIAL STREAMS") - # transfer_perennial_stream(sess, limit) - # - # if init or transfer_ephemeral_stream_flag: - # message("TRANSFERRING EPHEMERAL STREAMS") - # transfer_ephemeral_stream(sess, limit) - # - # if init or transfer_met_flag: - # message("TRANSFERRING METEOROLOGICAL") - # transfer_met(sess, limit) + if init or transfer_spring_flag: + message("TRANSFERRING SPRINGS") + transfer_springs(sess, limit) + + if init or transfer_perennial_stream_flag: + message("TRANSFERRING PERENNIAL STREAMS") + transfer_perennial_stream(sess, limit) + + if init or transfer_ephemeral_stream_flag: + message("TRANSFERRING EPHEMERAL STREAMS") + transfer_ephemeral_stream(sess, limit) + + if init or transfer_met_flag: + message("TRANSFERRING METEOROLOGICAL") + transfer_met(sess, limit) if init or transfer_contacts_flag: message("TRANSFERRING CONTACTS") From a182088abfb338c81c6dd55d9aae4883646ef1f3 Mon Sep 17 00:00:00 2001 From: jirhiker Date: Fri, 5 Sep 2025 03:27:13 +0000 Subject: [PATCH 27/46] Formatting changes --- transfers/contact_transfer.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/transfers/contact_transfer.py b/transfers/contact_transfer.py index 61da8e325..9d9c5f9bc 100644 --- a/transfers/contact_transfer.py +++ b/transfers/contact_transfer.py @@ -67,7 +67,7 @@ def transfer_contacts(session): add_first_contact(session, row, thing) session.commit() session.flush() - print(f'added first contact for PointID {row.PointID}') + print(f"added first contact for PointID {row.PointID}") except Exception as e: print( f"Skipping second contact for PointID {row.PointID} due to validation error: {e}" @@ -78,7 +78,7 @@ def transfer_contacts(session): add_second_contact(session, row, thing) session.commit() session.flush() - print(f'added second contact for PointID {row.PointID}') + print(f"added second contact for PointID {row.PointID}") except Exception as e: print( f"Skipping second contact for PointID {row.PointID} due to validation error: {e}" @@ -218,7 +218,6 @@ def add_second_contact(session, row, thing): ) - def _make_contact_and_assoc(session, data, thing): CreateContact.model_validate(data) @@ -231,4 +230,6 @@ def _make_contact_and_assoc(session, data, thing): session.add(assoc) session.add(contact) return contact + + # ============= EOF ============================================= From bd9bc86e2c93f3480423c5f1aedb43ef5ffaabfb Mon Sep 17 00:00:00 2001 From: jakeross Date: Fri, 5 Sep 2025 00:09:49 -0600 Subject: [PATCH 28/46] feat: add wells endpoint and health check to transfer service --- transfers/entrypoint.py | 16 +++++++++++----- transfers/well_transfer.py | 7 ++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/transfers/entrypoint.py b/transfers/entrypoint.py index 7c2aed583..d25f9e67f 100644 --- a/transfers/entrypoint.py +++ b/transfers/entrypoint.py @@ -16,16 +16,22 @@ from fastapi import FastAPI -from transfers.transfer import main_transfer +from core.dependencies import session_dependency +from transfers.well_transfer import transfer_wells app = FastAPI(title="Transfer Service") -@app.get("/trigger") -async def trigger(): - main_transfer() +@app.get("/wells") +async def wells(session: session_dependency): - return + results = transfer_wells(session, limit=50) + return results + + +@app.get("/health") +async def health(): + return {"status": "ok"} # ============= EOF ============================================= diff --git a/transfers/well_transfer.py b/transfers/well_transfer.py index 47d95d642..cf46da604 100644 --- a/transfers/well_transfer.py +++ b/transfers/well_transfer.py @@ -46,7 +46,8 @@ def transfer_wells(session, limit=None): n = len(wdf) start_time = time.time() - + results = {"n": n, } + made_things = [] for i, row in enumerate(wdf.itertuples()): if limit and i >= limit: print("Reached limit of", limit, "rows. Stopping migration.") @@ -107,6 +108,10 @@ def transfer_wells(session, limit=None): assoc.location = location assoc.thing = well session.add(assoc) + made_things.append(row.PointID) + + results["made_things"] = made_things + return results def transfer_wellscreens(session, limit=None): From 936814ec65f9f9703d686fb577767106c96730cc Mon Sep 17 00:00:00 2001 From: jirhiker Date: Fri, 5 Sep 2025 06:10:06 +0000 Subject: [PATCH 29/46] Formatting changes --- transfers/well_transfer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/transfers/well_transfer.py b/transfers/well_transfer.py index cf46da604..4c7c0b6e4 100644 --- a/transfers/well_transfer.py +++ b/transfers/well_transfer.py @@ -46,7 +46,9 @@ def transfer_wells(session, limit=None): n = len(wdf) start_time = time.time() - results = {"n": n, } + results = { + "n": n, + } made_things = [] for i, row in enumerate(wdf.itertuples()): if limit and i >= limit: From 0b09d6bab2e93a70358965bbc0b324840a85b6c0 Mon Sep 17 00:00:00 2001 From: jakeross Date: Fri, 5 Sep 2025 00:22:18 -0600 Subject: [PATCH 30/46] feat: add wells endpoint and health check to transfer service --- services/gcs_helper.py | 2 ++ transfer_test.sh | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 transfer_test.sh diff --git a/services/gcs_helper.py b/services/gcs_helper.py index e465aa9bb..158019e2f 100644 --- a/services/gcs_helper.py +++ b/services/gcs_helper.py @@ -42,6 +42,8 @@ def get_storage_client() -> storage.Client: # Create storage client client = storage.Client(credentials=creds) + elif settings.mode == "transfer": + client = storage.Client() else: client = storage.Client.from_service_account_json( os.environ.get("GOOGLE_APPLICATION_CREDENTIALS") diff --git a/transfer_test.sh b/transfer_test.sh new file mode 100644 index 000000000..73d320598 --- /dev/null +++ b/transfer_test.sh @@ -0,0 +1,2 @@ +curl -X GEt "https://nma-ocotillo-transfer-95715287188.us-central1.run.app/wells" \ +-H "Authorization: bearer $(gcloud auth print-identity-token)" \ No newline at end of file From ee11adeadf871ea5620b6b7ac74c6251b6622e59 Mon Sep 17 00:00:00 2001 From: jakeross Date: Fri, 5 Sep 2025 00:38:42 -0600 Subject: [PATCH 31/46] feat: update wells endpoint to support pagination with start index and limit --- transfers/entrypoint.py | 8 +++++--- transfers/well_transfer.py | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/transfers/entrypoint.py b/transfers/entrypoint.py index d25f9e67f..4759dfdb4 100644 --- a/transfers/entrypoint.py +++ b/transfers/entrypoint.py @@ -22,10 +22,12 @@ app = FastAPI(title="Transfer Service") -@app.get("/wells") -async def wells(session: session_dependency): +@app.post("/wells") +async def wells(session: session_dependency, + start_index: int, + limit: int=25, ): - results = transfer_wells(session, limit=50) + results = transfer_wells(session, start_index=start_index, limit=limit) return results diff --git a/transfers/well_transfer.py b/transfers/well_transfer.py index 4c7c0b6e4..ecfa7de39 100644 --- a/transfers/well_transfer.py +++ b/transfers/well_transfer.py @@ -36,13 +36,14 @@ ADDED = [] -def transfer_wells(session, limit=None): +def transfer_wells(session, start_index=0, limit=0): wdf = read_csv("WellData") ldf = read_csv("Location") ldf = ldf.drop(["PointID", "SSMA_TimeStamp"], axis=1) wdf = wdf.join(ldf.set_index("LocationId"), on="LocationId") wdf = wdf[wdf["SiteType"] == "GW"] wdf = wdf[wdf["Easting"].notna() & wdf["Northing"].notna()] + wdf = wdf.iloc[start_index:start_index+limit] n = len(wdf) start_time = time.time() From 6bc8f69fe5c71652ab6f96b08a6db463a1fad460 Mon Sep 17 00:00:00 2001 From: jirhiker Date: Fri, 5 Sep 2025 06:39:02 +0000 Subject: [PATCH 32/46] Formatting changes --- transfers/entrypoint.py | 8 +++++--- transfers/well_transfer.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/transfers/entrypoint.py b/transfers/entrypoint.py index 4759dfdb4..f7b9861c5 100644 --- a/transfers/entrypoint.py +++ b/transfers/entrypoint.py @@ -23,9 +23,11 @@ @app.post("/wells") -async def wells(session: session_dependency, - start_index: int, - limit: int=25, ): +async def wells( + session: session_dependency, + start_index: int, + limit: int = 25, +): results = transfer_wells(session, start_index=start_index, limit=limit) return results diff --git a/transfers/well_transfer.py b/transfers/well_transfer.py index ecfa7de39..c53cbac7d 100644 --- a/transfers/well_transfer.py +++ b/transfers/well_transfer.py @@ -43,7 +43,7 @@ def transfer_wells(session, start_index=0, limit=0): wdf = wdf.join(ldf.set_index("LocationId"), on="LocationId") wdf = wdf[wdf["SiteType"] == "GW"] wdf = wdf[wdf["Easting"].notna() & wdf["Northing"].notna()] - wdf = wdf.iloc[start_index:start_index+limit] + wdf = wdf.iloc[start_index : start_index + limit] n = len(wdf) start_time = time.time() From 764e39f7e8c4ea164c35fd339a74e4501bea0599 Mon Sep 17 00:00:00 2001 From: jakeross Date: Fri, 5 Sep 2025 00:45:32 -0600 Subject: [PATCH 33/46] feat: update health check endpoint to use POST method with JSON payload --- transfer_test.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/transfer_test.sh b/transfer_test.sh index 73d320598..6e87dea94 100644 --- a/transfer_test.sh +++ b/transfer_test.sh @@ -1,2 +1,4 @@ -curl -X GEt "https://nma-ocotillo-transfer-95715287188.us-central1.run.app/wells" \ --H "Authorization: bearer $(gcloud auth print-identity-token)" \ No newline at end of file +curl -X POST "https://nma-ocotillo-transfer-95715287188.us-central1.run.app/health" \ +-H "Authorization: bearer $(gcloud auth print-identity-token)" \ +-H "Content-Type: application/json" \ +-d '{"limit": "10"}' \ No newline at end of file From 37543c4649be668940ae23095e88718df61b4108 Mon Sep 17 00:00:00 2001 From: jakeross Date: Tue, 9 Sep 2025 15:03:11 -0600 Subject: [PATCH 34/46] feat: add multiple transfer endpoints for assets, contacts, groups, and more --- transfers/entrypoint.py | 78 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 6 deletions(-) diff --git a/transfers/entrypoint.py b/transfers/entrypoint.py index f7b9861c5..b9ab2c070 100644 --- a/transfers/entrypoint.py +++ b/transfers/entrypoint.py @@ -17,25 +17,91 @@ from fastapi import FastAPI from core.dependencies import session_dependency -from transfers.well_transfer import transfer_wells +from transfers.asset_transfer import transfer_assets_testing +from transfers.contact_transfer import transfer_contacts +from transfers.group_transfer import transfer_groups +from transfers.link_ids_transfer import transfer_link_ids, transfer_link_ids_welldata +from transfers.thing_transfer import transfer_met, transfer_ephemeral_stream, transfer_perennial_stream, \ + transfer_springs +from transfers.transfer import message +from transfers.waterlevels_transfer import transfer_water_levels +from transfers.well_transfer import transfer_wells, cleanup_wells app = FastAPI(title="Transfer Service") @app.post("/wells") async def wells( - session: session_dependency, - start_index: int, - limit: int = 25, + session: session_dependency, + start_index: int, + limit: int = 25, ): - results = transfer_wells(session, start_index=start_index, limit=limit) return results +@app.post('/spring') +async def _(session: session_dependency, limit: int = 25): + message("TRANSFERRING SPRINGS") + transfer_springs(session, limit) + + +@app.post('/perennial_stream') +async def _(session: session_dependency, limit: int = 25): + message("TRANSFERRING PERENNIAL STREAMS") + transfer_perennial_stream(session, limit) + + +@app.post('/ephemeral_stream') +async def _(session: session_dependency, limit: int = 25): + message("TRANSFERRING EPHEMERAL STREAMS") + transfer_ephemeral_stream(session, limit) + + +@app.post('/met') +async def _(session: session_dependency, limit: int = 25): + message("TRANSFERRING METEOROLOGICAL") + transfer_met(session, limit) + + +@app.post('/contacts') +async def _(session: session_dependency): + message("TRANSFERRING CONTACTS") + transfer_contacts(session) + + +@app.post('/waterlevels') +async def _(session: session_dependency): + message("TRANSFERRING WATER LEVELS") + transfer_water_levels(session) + + +@app.post('/link_ids') +async def _(session: session_dependency): + message("TRANSFERRING LINK IDS") + transfer_link_ids(session) + transfer_link_ids_welldata(session) + + +@app.post('assets') +async def _transfer_assets(session: session_dependency): + message("TRANSFERRING ASSETS") + transfer_assets_testing(session) + + +@app.post('/groups') +async def _transfer_groups(session: session_dependency): + message("TRANSFERRING GROUPS") + transfer_groups(session) + + +@app.post('/cleanup_wells') +async def _cleanup_wells(session: session_dependency): + cleanup_wells(session) + + @app.get("/health") async def health(): return {"status": "ok"} - # ============= EOF ============================================= From c6d88362b86824714087b5f0b8030799806d4f3b Mon Sep 17 00:00:00 2001 From: jirhiker Date: Tue, 9 Sep 2025 21:03:32 +0000 Subject: [PATCH 35/46] Formatting changes --- transfers/entrypoint.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/transfers/entrypoint.py b/transfers/entrypoint.py index b9ab2c070..082fde659 100644 --- a/transfers/entrypoint.py +++ b/transfers/entrypoint.py @@ -21,8 +21,12 @@ from transfers.contact_transfer import transfer_contacts from transfers.group_transfer import transfer_groups from transfers.link_ids_transfer import transfer_link_ids, transfer_link_ids_welldata -from transfers.thing_transfer import transfer_met, transfer_ephemeral_stream, transfer_perennial_stream, \ - transfer_springs +from transfers.thing_transfer import ( + transfer_met, + transfer_ephemeral_stream, + transfer_perennial_stream, + transfer_springs, +) from transfers.transfer import message from transfers.waterlevels_transfer import transfer_water_levels from transfers.well_transfer import transfer_wells, cleanup_wells @@ -32,70 +36,70 @@ @app.post("/wells") async def wells( - session: session_dependency, - start_index: int, - limit: int = 25, + session: session_dependency, + start_index: int, + limit: int = 25, ): results = transfer_wells(session, start_index=start_index, limit=limit) return results -@app.post('/spring') +@app.post("/spring") async def _(session: session_dependency, limit: int = 25): message("TRANSFERRING SPRINGS") transfer_springs(session, limit) -@app.post('/perennial_stream') +@app.post("/perennial_stream") async def _(session: session_dependency, limit: int = 25): message("TRANSFERRING PERENNIAL STREAMS") transfer_perennial_stream(session, limit) -@app.post('/ephemeral_stream') +@app.post("/ephemeral_stream") async def _(session: session_dependency, limit: int = 25): message("TRANSFERRING EPHEMERAL STREAMS") transfer_ephemeral_stream(session, limit) -@app.post('/met') +@app.post("/met") async def _(session: session_dependency, limit: int = 25): message("TRANSFERRING METEOROLOGICAL") transfer_met(session, limit) -@app.post('/contacts') +@app.post("/contacts") async def _(session: session_dependency): message("TRANSFERRING CONTACTS") transfer_contacts(session) -@app.post('/waterlevels') +@app.post("/waterlevels") async def _(session: session_dependency): message("TRANSFERRING WATER LEVELS") transfer_water_levels(session) -@app.post('/link_ids') +@app.post("/link_ids") async def _(session: session_dependency): message("TRANSFERRING LINK IDS") transfer_link_ids(session) transfer_link_ids_welldata(session) -@app.post('assets') +@app.post("assets") async def _transfer_assets(session: session_dependency): message("TRANSFERRING ASSETS") transfer_assets_testing(session) -@app.post('/groups') +@app.post("/groups") async def _transfer_groups(session: session_dependency): message("TRANSFERRING GROUPS") transfer_groups(session) -@app.post('/cleanup_wells') +@app.post("/cleanup_wells") async def _cleanup_wells(session: session_dependency): cleanup_wells(session) @@ -104,4 +108,5 @@ async def _cleanup_wells(session: session_dependency): async def health(): return {"status": "ok"} + # ============= EOF ============================================= From 49225bf6e528721f4ef3307a60930bfe3302227a Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Thu, 11 Sep 2025 10:02:29 -0600 Subject: [PATCH 36/46] fix: set dtype for OSEWelltagID --- transfers/link_ids_transfer.py | 2 +- transfers/well_transfer.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/transfers/link_ids_transfer.py b/transfers/link_ids_transfer.py index e607def31..fc1cd8a97 100644 --- a/transfers/link_ids_transfer.py +++ b/transfers/link_ids_transfer.py @@ -26,7 +26,7 @@ def transfer_link_ids_welldata(session): - ldf = read_csv("WellData") + ldf = read_csv("WellData", dtype={"OSEWelltagID": str}) ldf = filter_to_valid_point_ids(session, ldf) diff --git a/transfers/well_transfer.py b/transfers/well_transfer.py index eb23f8b6b..683943213 100644 --- a/transfers/well_transfer.py +++ b/transfers/well_transfer.py @@ -14,7 +14,6 @@ # limitations under the License. # =============================================================================== import time - import numpy as np import pandas as pd from pydantic import ValidationError @@ -37,14 +36,13 @@ def transfer_wells(session, start_index=0, limit=0): - wdf = read_csv("WellData") + wdf = read_csv("WellData", dtype={"OSEWelltagID": str}) ldf = read_csv("Location") ldf = ldf.drop(["PointID", "SSMA_TimeStamp"], axis=1) wdf = wdf.join(ldf.set_index("LocationId"), on="LocationId") wdf = wdf[wdf["SiteType"] == "GW"] wdf = wdf[wdf["Easting"].notna() & wdf["Northing"].notna()] wdf = wdf.iloc[start_index : start_index + limit] - n = len(wdf) start_time = time.time() results = { @@ -89,7 +87,7 @@ def transfer_wells(session, start_index=0, limit=0): }, thing_type="water well", ) - + logger.info("adding well") # TODO: use current use LUT to get well type # wt = row.Meaning From b0d133882228022c6a05d7ec9df16078ac47b513 Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Thu, 11 Sep 2025 10:02:59 -0600 Subject: [PATCH 37/46] feat: redirect errors to log --- transfers/util.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/transfers/util.py b/transfers/util.py index 530423a43..38a45f169 100644 --- a/transfers/util.py +++ b/transfers/util.py @@ -28,6 +28,23 @@ from db import Thing, Location from services.gcs_helper import get_storage_bucket +import sys + + +class StreamToLogger: + def __init__(self, logger, level): + self.logger = logger + self.level = level + self.linebuf = "" + + def write(self, buf): + for line in buf.rstrip().splitlines(): + self.logger.log(self.level, line.rstrip()) + + def flush(self): + pass + + log_filename = f"transfers/transfer_{datetime.now():%Y-%m-%dT%Hh%Mm%Ss}.log" @@ -41,14 +58,21 @@ ) logger = logging.getLogger(__name__) +# redirect stderr to the logger +sys.stderr = StreamToLogger(logger, logging.ERROR) + TRANSFORMERS = {} -def read_csv(name: str) -> pd.DataFrame: +def read_csv(name: str, dtype: dict | None = None) -> pd.DataFrame: bucket = get_storage_bucket() blob = bucket.blob(f"nma_csv/{name}.csv") data = blob.download_as_bytes() - return pd.read_csv(io.BytesIO(data)) + + if dtype: + return pd.read_csv(io.BytesIO(data), dtype=dtype) + else: + return pd.read_csv(io.BytesIO(data)) def transform_srid(geometry, source_srid, target_srid): From 8087192a47d80678599c579a03087cea38f4af1f Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Thu, 11 Sep 2025 10:03:22 -0600 Subject: [PATCH 38/46] fix: set limit correctly for well transfer --- transfers/transfer.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/transfers/transfer.py b/transfers/transfer.py index a0057dc53..efd71c103 100644 --- a/transfers/transfer.py +++ b/transfers/transfer.py @@ -47,12 +47,12 @@ def erase_and_initalize(session: Session) -> None: def message(msg, pad=10): pad = "*" * pad - print(f"{pad} {msg} {pad}\n") + logger.info("") + logger.info(f"{pad} {msg} {pad}") def main_transfer(): logger.info("Starting transfer") - logger.info("") init = True @@ -76,44 +76,37 @@ def main_transfer(): if init or transfer_well_flag: message("TRANSFERRING WELLS") - transfer_wells(sess, limit) + transfer_wells(sess, limit=limit) transfer_wellscreens(sess) # if init or transfer_spring_flag: message("TRANSFERRING SPRINGS") transfer_springs(sess, limit) - logger.info("") if init or transfer_perennial_stream_flag: message("TRANSFERRING PERENNIAL STREAMS") transfer_perennial_stream(sess, limit) - logger.info("") if init or transfer_ephemeral_stream_flag: message("TRANSFERRING EPHEMERAL STREAMS") transfer_ephemeral_stream(sess, limit) - logger.info("") if init or transfer_met_flag: message("TRANSFERRING METEOROLOGICAL") transfer_met(sess, limit) - logger.info("") if init or transfer_contacts_flag: message("TRANSFERRING CONTACTS") transfer_contacts(sess) - logger.info("") if init or transfer_waterlevels_flag: message("TRANSFERRING WATER LEVELS") transfer_water_levels(sess) - logger.info("") if init or transfer_link_ids_flag: message("TRANSFERRING LINK IDS") transfer_link_ids(sess) transfer_link_ids_welldata(sess) - logger.info("") # if init or transfer_assets_flag: # message("TRANSFERRING ASSETS") @@ -122,7 +115,6 @@ def main_transfer(): if init or transfer_groups_flag: message("TRANSFERRING GROUPS") transfer_groups(sess) - logger.info("") # if init or cleanup_wells_flag: # cleanup_wells(sess) From 076c024549bc9c0f34a8013edb2152a1e5cb71af Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Thu, 11 Sep 2025 10:44:16 -0600 Subject: [PATCH 39/46] fix: remove print debugging statement --- transfers/well_transfer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/transfers/well_transfer.py b/transfers/well_transfer.py index 683943213..19d726bdf 100644 --- a/transfers/well_transfer.py +++ b/transfers/well_transfer.py @@ -87,7 +87,6 @@ def transfer_wells(session, start_index=0, limit=0): }, thing_type="water well", ) - logger.info("adding well") # TODO: use current use LUT to get well type # wt = row.Meaning From 5e7a2c2bc2ebc108dd9a8299f789c981460c14ac Mon Sep 17 00:00:00 2001 From: Jacob Brown Date: Thu, 11 Sep 2025 11:24:06 -0600 Subject: [PATCH 40/46] feat: validate addresses with pydantic --- transfers/contact_transfer.py | 54 +++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/transfers/contact_transfer.py b/transfers/contact_transfer.py index eeacaaa3e..41a46d1eb 100644 --- a/transfers/contact_transfer.py +++ b/transfers/contact_transfer.py @@ -18,7 +18,7 @@ from transfers.util import read_csv, filter_to_valid_point_ids, logger from db import Thing, Contact, ThingContactAssociation, Email, Phone, Address -from schemas.contact import CreateContact +from schemas.contact import CreateContact, CreateAddress def extract_owner_role(comment): @@ -148,29 +148,39 @@ def add_first_contact(session, row, thing): ) if row.MailingAddress: - # TODO: use Pydantic to validate MailingAddress - contact.addresses.append( - Address( - address_line_1=row.MailingAddress, - city=row.MailCity, - state=row.MailState, - postal_code=row.MailZipCode, - address_type="Mailing", - release_status=release_status, + address_data = { + "address_line_1": row.MailingAddress, + "city": row.MailCity, + "state": row.MailState, + "postal_code": row.MailZipCode, + "address_type": "Mailing", + "release_status": release_status, + } + try: + CreateAddress.model_validate(address_data) + contact.addresses.append(Address(**address_data)) + + except Exception as e: + logger.warning( + f"Skipping mailing address for first contact {name}. Validation error: {e}" ) - ) - contact.addresses.append( - # TODO: use Pydantic to validate PhysicalAddress - Address( - address_line_1=row.PhysicalAddress, - city=row.PhysicalCity, - state=row.PhysicalState, - postal_code=row.PhysicalZipCode, - address_type="Physical", - release_status=release_status, - ) - ) + if row.PhysicalAddress: + try: + address_data = { + "address_line_1": row.PhysicalAddress, + "city": row.PhysicalCity, + "state": row.PhysicalState, + "postal_code": row.PhysicalZipCode, + "address_type": "Physical", + "release_status": release_status, + } + CreateAddress.model_validate(address_data) + contact.addresses.append(Address(**address_data)) + except Exception as e: + logger.warning( + f"Skipping physical address for first contact {name}. Validation error: {e}" + ) def add_second_contact(session, row, thing): From 6a3e68a3f776ac92146ecd9c755cb0211a89725b Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 11 Sep 2025 19:53:16 -0600 Subject: [PATCH 41/46] feat: update deployment configuration to trigger on staging branch --- .github/workflows/staging_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/staging_deploy.yml b/.github/workflows/staging_deploy.yml index c748662fe..6ef29f165 100644 --- a/.github/workflows/staging_deploy.yml +++ b/.github/workflows/staging_deploy.yml @@ -2,7 +2,7 @@ name: CD (Staging) on: push: - branches: [pre-production] + branches: [staging] permissions: contents: write From 8c058dd5e62fe99df959cee9c58c541fb4c0f7b2 Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 11 Sep 2025 20:02:27 -0600 Subject: [PATCH 42/46] feat: comment out old version cleanup step in deployment configuration --- .github/workflows/staging_deploy.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/staging_deploy.yml b/.github/workflows/staging_deploy.yml index 6ef29f165..b823cf8b0 100644 --- a/.github/workflows/staging_deploy.yml +++ b/.github/workflows/staging_deploy.yml @@ -66,16 +66,16 @@ jobs: gcloud app deploy app.yaml --quiet --project ${{ secrets.GCP_PROJECT_ID }} # Clean up old versions - delete only the oldest version, one created and one destroyed - - name: Clean up oldest version - run: | - OLDEST_VERSION=$(gcloud app versions list --service=ocotillo-api-staging --project=${{ secrets.GCP_PROJECT_ID}} --format="value(id)" --sort-by="version.createTime" | head -n 1) - if [ ! -z "$OLDEST_VERSION" ]; then - echo "Deleting oldest version: $OLDEST_VERSION" - gcloud app versions delete $OLDEST_VERSION --service=ocotillo-api-staging --project=${{ secrets.GCP_PROJECT_ID }} --quiet - echo "Deleted oldest version: $OLDEST_VERSION" - else - echo "No versions to delete" - fi +# - name: Clean up oldest version +# run: | +# OLDEST_VERSION=$(gcloud app versions list --service=ocotillo-api-staging --project=${{ secrets.GCP_PROJECT_ID}} --format="value(id)" --sort-by="version.createTime" | head -n 1) +# if [ ! -z "$OLDEST_VERSION" ]; then +# echo "Deleting oldest version: $OLDEST_VERSION" +# gcloud app versions delete $OLDEST_VERSION --service=ocotillo-api-staging --project=${{ secrets.GCP_PROJECT_ID }} --quiet +# echo "Deleted oldest version: $OLDEST_VERSION" +# else +# echo "No versions to delete" +# fi - name: Remove app.yaml run: | From f159b3a82a37d3741949f6997af24937e3bfa44f Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 11 Sep 2025 20:05:33 -0600 Subject: [PATCH 43/46] feat: version cleanup --- .github/workflows/staging_deploy.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/staging_deploy.yml b/.github/workflows/staging_deploy.yml index b823cf8b0..6ef29f165 100644 --- a/.github/workflows/staging_deploy.yml +++ b/.github/workflows/staging_deploy.yml @@ -66,16 +66,16 @@ jobs: gcloud app deploy app.yaml --quiet --project ${{ secrets.GCP_PROJECT_ID }} # Clean up old versions - delete only the oldest version, one created and one destroyed -# - name: Clean up oldest version -# run: | -# OLDEST_VERSION=$(gcloud app versions list --service=ocotillo-api-staging --project=${{ secrets.GCP_PROJECT_ID}} --format="value(id)" --sort-by="version.createTime" | head -n 1) -# if [ ! -z "$OLDEST_VERSION" ]; then -# echo "Deleting oldest version: $OLDEST_VERSION" -# gcloud app versions delete $OLDEST_VERSION --service=ocotillo-api-staging --project=${{ secrets.GCP_PROJECT_ID }} --quiet -# echo "Deleted oldest version: $OLDEST_VERSION" -# else -# echo "No versions to delete" -# fi + - name: Clean up oldest version + run: | + OLDEST_VERSION=$(gcloud app versions list --service=ocotillo-api-staging --project=${{ secrets.GCP_PROJECT_ID}} --format="value(id)" --sort-by="version.createTime" | head -n 1) + if [ ! -z "$OLDEST_VERSION" ]; then + echo "Deleting oldest version: $OLDEST_VERSION" + gcloud app versions delete $OLDEST_VERSION --service=ocotillo-api-staging --project=${{ secrets.GCP_PROJECT_ID }} --quiet + echo "Deleted oldest version: $OLDEST_VERSION" + else + echo "No versions to delete" + fi - name: Remove app.yaml run: | From c1cc3f948971123ae90cf207f99f2e15e9658c67 Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 11 Sep 2025 20:12:34 -0600 Subject: [PATCH 44/46] feat: update deployment configuration to use vars for non-sensitive information --- .github/workflows/staging_deploy.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/staging_deploy.yml b/.github/workflows/staging_deploy.yml index 6ef29f165..7b5d7c2b9 100644 --- a/.github/workflows/staging_deploy.yml +++ b/.github/workflows/staging_deploy.yml @@ -50,28 +50,28 @@ jobs: echo " MODE: \"production\"" >> app.yaml echo " DB_DRIVER: \"cloudsql\"" >> app.yaml echo " CLOUD_SQL_INSTANCE_NAME: \"${{ secrets.CLOUD_SQL_INSTANCE_NAME }}\"" >> app.yaml - echo " CLOUD_SQL_DATABASE: \"${{ secrets.CLOUD_SQL_DATABASE }}\"" >> app.yaml + echo " CLOUD_SQL_DATABASE: \"${{ vars.CLOUD_SQL_DATABASE }}\"" >> app.yaml echo " CLOUD_SQL_USER: \"${{ secrets.CLOUD_SQL_USER }}\"" >> app.yaml echo " CLOUD_SQL_PASSWORD: \"${{ secrets.CLOUD_SQL_PASSWORD }}\"" >> app.yaml echo " GCS_SERVICE_ACCOUNT_KEY: \"${{ secrets.GCS_SERVICE_ACCOUNT_KEY }}\"" >> app.yaml - echo " GCS_BUCKET_NAME: \"${{secrets.GCS_BUCKET_NAME}}\"" >> app.yaml - echo " AUTHENTIK_URL: \"${{secrets.AUTHENTIK_URL}}\"" >> app.yaml - echo " AUTHENTIK_CLIENT_ID: \"${{secrets.AUTHENTIK_CLIENT_ID}}\"" >> app.yaml - echo " AUTHENTIK_AUTHORIZE_URL: \"${{secrets.AUTHENTIK_AUTHORIZE_URL}}\"" >> app.yaml - echo " AUTHENTIK_TOKEN_URL: \"${{secrets.AUTHENTIK_TOKEN_URL}}\"" >> app.yaml + echo " GCS_BUCKET_NAME: \"${{vars.GCS_BUCKET_NAME}}\"" >> app.yaml + echo " AUTHENTIK_URL: \"${{vars.AUTHENTIK_URL}}\"" >> app.yaml + echo " AUTHENTIK_CLIENT_ID: \"${{vars.AUTHENTIK_CLIENT_ID}}\"" >> app.yaml + echo " AUTHENTIK_AUTHORIZE_URL: \"${{vars.AUTHENTIK_AUTHORIZE_URL}}\"" >> app.yaml + echo " AUTHENTIK_TOKEN_URL: \"${{vars.AUTHENTIK_TOKEN_URL}}\"" >> app.yaml - name: Deploy to Google Cloud run: | - gcloud app deploy app.yaml --quiet --project ${{ secrets.GCP_PROJECT_ID }} + gcloud app deploy app.yaml --quiet --project ${{ vars.GCP_PROJECT_ID }} # Clean up old versions - delete only the oldest version, one created and one destroyed - name: Clean up oldest version run: | - OLDEST_VERSION=$(gcloud app versions list --service=ocotillo-api-staging --project=${{ secrets.GCP_PROJECT_ID}} --format="value(id)" --sort-by="version.createTime" | head -n 1) + OLDEST_VERSION=$(gcloud app versions list --service=ocotillo-api-staging --project=${{ vars.GCP_PROJECT_ID}} --format="value(id)" --sort-by="version.createTime" | head -n 1) if [ ! -z "$OLDEST_VERSION" ]; then echo "Deleting oldest version: $OLDEST_VERSION" - gcloud app versions delete $OLDEST_VERSION --service=ocotillo-api-staging --project=${{ secrets.GCP_PROJECT_ID }} --quiet + gcloud app versions delete $OLDEST_VERSION --service=ocotillo-api-staging --project=${{ vars.GCP_PROJECT_ID }} --quiet echo "Deleted oldest version: $OLDEST_VERSION" else echo "No versions to delete" From 08d7fb8ac083e48a82fcecb14517d09fc5671cdf Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 11 Sep 2025 20:17:14 -0600 Subject: [PATCH 45/46] feat: rename staging_deploy.yml to CD_staging.yml --- .github/workflows/{staging_deploy.yml => CD_staging.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{staging_deploy.yml => CD_staging.yml} (100%) diff --git a/.github/workflows/staging_deploy.yml b/.github/workflows/CD_staging.yml similarity index 100% rename from .github/workflows/staging_deploy.yml rename to .github/workflows/CD_staging.yml From ab633acbcd7884d0b64ca4b16c4435ae30bf9644 Mon Sep 17 00:00:00 2001 From: jakeross Date: Thu, 11 Sep 2025 20:18:58 -0600 Subject: [PATCH 46/46] feat: add production deployment configuration for Google Cloud --- .github/workflows/CD_production.yml | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 .github/workflows/CD_production.yml diff --git a/.github/workflows/CD_production.yml b/.github/workflows/CD_production.yml new file mode 100644 index 000000000..3812aa85f --- /dev/null +++ b/.github/workflows/CD_production.yml @@ -0,0 +1,94 @@ +name: CD (Staging) + +on: + push: + branches: [production] + +permissions: + contents: write + +jobs: + staging-deploy: + + runs-on: ubuntu-latest + environment: production + + steps: + - name: Check out source repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install uv in container + uses: astral-sh/setup-uv@v6 + with: + version: "latest" + + - name: Generate requirements.txt + run: | + uv export -o requirements.txt + + - name: Authenticate to Google Cloud + uses: 'google-github-actions/auth@v2' + with: + credentials_json: ${{ secrets.CLOUD_DEPLOY_SERVICE_ACCOUNT_KEY }} + + # Uses Google Cloud Secret Manager to store secret credentials + - name: Create app.yaml + run: | + echo "service: ocotillo-api" > app.yaml + echo "runtime: python313" >> app.yaml + echo "entrypoint: gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app" >> app.yaml + echo "instance_class: F4" >> app.yaml + echo "inbound_services:" >> app.yaml + echo " - warmup" >> app.yaml + echo "automatic_scaling:" >> app.yaml + echo " min_instances: 0" >> app.yaml + echo " max_instances: 10" >> app.yaml + echo "" >> app.yaml + echo "env_variables:" >> app.yaml + echo " MODE: \"production\"" >> app.yaml + echo " DB_DRIVER: \"cloudsql\"" >> app.yaml + echo " CLOUD_SQL_INSTANCE_NAME: \"${{ secrets.CLOUD_SQL_INSTANCE_NAME }}\"" >> app.yaml + echo " CLOUD_SQL_DATABASE: \"${{ vars.CLOUD_SQL_DATABASE }}\"" >> app.yaml + echo " CLOUD_SQL_USER: \"${{ secrets.CLOUD_SQL_USER }}\"" >> app.yaml + echo " CLOUD_SQL_PASSWORD: \"${{ secrets.CLOUD_SQL_PASSWORD }}\"" >> app.yaml + echo " GCS_SERVICE_ACCOUNT_KEY: \"${{ secrets.GCS_SERVICE_ACCOUNT_KEY }}\"" >> app.yaml + echo " GCS_BUCKET_NAME: \"${{vars.GCS_BUCKET_NAME}}\"" >> app.yaml + echo " AUTHENTIK_URL: \"${{vars.AUTHENTIK_URL}}\"" >> app.yaml + echo " AUTHENTIK_CLIENT_ID: \"${{vars.AUTHENTIK_CLIENT_ID}}\"" >> app.yaml + echo " AUTHENTIK_AUTHORIZE_URL: \"${{vars.AUTHENTIK_AUTHORIZE_URL}}\"" >> app.yaml + echo " AUTHENTIK_TOKEN_URL: \"${{vars.AUTHENTIK_TOKEN_URL}}\"" >> app.yaml + + + - name: Deploy to Google Cloud + run: | + gcloud app deploy app.yaml --quiet --project ${{ vars.GCP_PROJECT_ID }} + + # Clean up old versions - delete only the oldest version, one created and one destroyed + - name: Clean up oldest version + run: | + OLDEST_VERSION=$(gcloud app versions list --service=ocotillo-api --project=${{ vars.GCP_PROJECT_ID}} --format="value(id)" --sort-by="version.createTime" | head -n 1) + if [ ! -z "$OLDEST_VERSION" ]; then + echo "Deleting oldest version: $OLDEST_VERSION" + gcloud app versions delete $OLDEST_VERSION --service=ocotillo-api --project=${{ vars.GCP_PROJECT_ID }} --quiet + echo "Deleted oldest version: $OLDEST_VERSION" + else + echo "No versions to delete" + fi + + - name: Remove app.yaml + run: | + rm app.yaml + + # Use PR author's username as git user name + - name: Set up git user + run: | + git config --global user.name "${{ github.actor }}" + git config --global user.email "${{ github.actor }}@users.noreply.github.com" + + # ":" are not alloed in git tags, so replace with "-" + - name: Tag commit + run: | + git tag -a "production-deploy-$(date -u +%Y-%m-%d)T$(date -u +%H-%M-%S%z)" -m "staging gcloud deployment: $(date -u +%Y-%m-%d)T$(date -u +%H:%M:%S%z)" + git push origin --tags \ No newline at end of file