From 563fd71c5e7d03b92c5a843522cf3c7ae04bf159 Mon Sep 17 00:00:00 2001 From: Hagen Fritz Date: Wed, 28 May 2025 13:25:25 -0500 Subject: [PATCH 1/3] feat: clean up directory snippets --- snippets/directory.ipynb | 900 ++++++++++++++++++++++++++++++++++++++- snippets/find_user.py | 55 --- snippets/get_users.py | 54 --- snippets/get_vendors.py | 71 --- snippets/trades.py | 48 --- 5 files changed, 889 insertions(+), 239 deletions(-) delete mode 100644 snippets/find_user.py delete mode 100644 snippets/get_users.py delete mode 100644 snippets/get_vendors.py delete mode 100644 snippets/trades.py diff --git a/snippets/directory.ipynb b/snippets/directory.ipynb index ca79ccc..8c709a9 100644 --- a/snippets/directory.ipynb +++ b/snippets/directory.ipynb @@ -78,9 +78,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "8089 2783683\n" + ] + } + ], "source": [ "company = connection.companies.find(identifier=company_name)\n", "project = connection.projects.find(\n", @@ -123,6 +131,86 @@ " * list where each value is a dict with a user's information" ] }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of trades: 57\n", + "Acoustical Ceilings\n", + "Applied Fireproofing\n", + "Asphalt Paving/Patch\n", + "Carpentry\n", + "Casework/Millwork\n", + "Communications - Data/Com\n", + "Concrete\n", + "Concrete - Coring\n", + "Concrete - Post Tension\n", + "Concrete - Rebar\n", + "Concrete - Saw Cutting\n", + "Concrete - Scanning\n", + "Demolition\n", + "Doors\n", + "Doors/Frames/Hardware\n", + "Drywall & Finish\n", + "Electrical\n", + "Elevators\n", + "Engineering - Architectural\n", + "Engineering - Civil\n", + "Engineering - Electrical\n", + "Engineering - Fire Protection\n", + "Finish Carpentry\n", + "Fire Protection\n", + "Flooring\n", + "Framing\n", + "General Contractor\n", + "Grading & Excavation\n", + "HVAC\n", + "Insulation\n", + "Landscape/Irrigation\n", + "Lath & Plaster/Stucco\n", + "Lead Shielding\n", + "Masonry\n", + "Masonry - Brick\n", + "Masonry - Concrete/CMU\n", + "Mechanical\n", + "Medical Gas\n", + "Metal Fabrications\n", + "Metal Panels\n", + "Misc Metals\n", + "Moisture/Thermal Protection\n", + "Paint\n", + "Plumbing\n", + "RF Shielding\n", + "Roofing\n", + "Site Demo\n", + "Specialties\n", + "Structural Steel\n", + "Systems Furniture\n", + "Tile\n", + "Toilet Partitions\n", + "Wall Coverings\n", + "Wall & Door Protection\n", + "Waterproofing\n", + "Windows\n", + "Window Shades\n" + ] + } + ], + "source": [ + "trades_companies = connection.directory.trades.get(company_id=company[\"id\"])\n", + "\n", + "print(\"Number of trades:\", len(trades_companies))\n", + "for trade_company in trades_companies:\n", + " print(trade_company[\"name\"])\n", + "\n", + "# print(json.dumps(trades_companies[0],indent=4))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -141,6 +229,43 @@ " * trade-specific dictionary" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"id\": 8089,\n", + " \"is_active\": true,\n", + " \"logo_url\": \"https://storage.procore.com/api/v5/files/us-east-1/pro-core.com/prostore/20170303162530_production_1172406419.png?companyId=8089&toolName=admin&itemType=company_logo&itemId=8089&fileType=prostore_file&fileId=1172406419&sig=378d966ae19975b1154766793d7c5450a6965cb20fee333b28e4ca8fe8b5b688\",\n", + " \"my_company\": false,\n", + " \"name\": \"Rogers-O`Brien Construction\",\n", + " \"pcn_business_experience\": null\n", + "}\n", + "623082: Window Shades\n", + "{\n", + " \"id\": 623082,\n", + " \"name\": \"Window Shades\",\n", + " \"active\": true,\n", + " \"updated_at\": \"2021-01-18T19:49:06Z\"\n", + "}\n" + ] + } + ], + "source": [ + "trade = connection.directory.trades.find(\n", + " company_id=company[\"id\"],\n", + " user_id=\"Window Shades\"\n", + ")\n", + "\n", + "print(f\"{trade['id']}: {trade['name']}\")\n", + "print(json.dumps(trade,indent=4))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -179,9 +304,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "18457\n" + ] + } + ], "source": [ "users = connection.directory.users.get(\n", " company_id=company[\"id\"],\n", @@ -213,9 +346,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'avatar': 'https://storage.procore.com/api/v5/files/us-east-1/pro-core.com/companies/9808/01HP2HEBNN5BH4HEP1ZCWY4FTP?companyId=8089&sig=91ecf57b431a98691a650e4bf69db0d11b2b10cae4c6fd08596d01993ac9de5b', 'country_code': 'US', 'email_address': 'hfritz@r-o.com', 'email_signature': 'Sent From Procore.', 'employee_id': '11986', 'first_name': 'Hagen', 'name': 'Hagen Fritz', 'id': 8780450, 'initials': 'HF', 'is_active': True, 'is_employee': True, 'job_title': 'R&D Engineer II', 'last_login_at': '2025-05-28T16:02:16Z', 'last_name': 'Fritz', 'default_permission_template_id': 952729, 'company_permission_template_id': 959814, 'created_at': '2022-06-21T18:47:25Z', 'updated_at': '2025-05-08T13:03:13Z', 'is_insurance_manager': False, 'add_to_new_projects': False, 'locale': '', 'vendor': {'id': 19174982, 'name': \"Rogers-O'Brien Construction Company\"}}\n" + ] + } + ], "source": [ "user = connection.directory.users.show(\n", " company_id=company[\"id\"],\n", @@ -256,9 +397,45 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'id': 8780450, 'name': 'Hagen Fritz', 'first_name': 'Hagen', 'initials': 'HF', 'last_name': 'Fritz', 'business_id': None, 'address': None, 'avatar': 'https://storage.procore.com/api/v5/files/us-east-1/pro-core.com/companies/9808/01HP2HEBNN5BH4HEP1ZCWY4FTP?companyId=8089&sig=91ecf57b431a98691a650e4bf69db0d11b2b10cae4c6fd08596d01993ac9de5b', 'business_phone': None, 'business_phone_extension': None, 'city': None, 'contact_id': 25212255, 'country_code': 'US', 'email_address': 'hfritz@r-o.com', 'employee_id': '11986', 'erp_integrated_accountant': False, 'fax_number': None, 'is_active': True, 'is_employee': True, 'job_title': 'R&D Engineer II', 'mobile_phone': None, 'origin_id': None, 'state_code': None, 'zip': None, 'last_activated_at': None, 'work_classification_id': None, 'vendor': {'id': 19174982, 'name': \"Rogers-O'Brien Construction Company\"}}\n" + ] + }, + { + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[9]\u001b[39m\u001b[32m, line 8\u001b[39m\n\u001b[32m 6\u001b[39m \u001b[38;5;28mprint\u001b[39m(user_by_id)\n\u001b[32m 7\u001b[39m \u001b[38;5;66;03m# Find by email\u001b[39;00m\n\u001b[32m----> \u001b[39m\u001b[32m8\u001b[39m user_by_email = \u001b[43mconnection\u001b[49m\u001b[43m.\u001b[49m\u001b[43mdirectory\u001b[49m\u001b[43m.\u001b[49m\u001b[43musers\u001b[49m\u001b[43m.\u001b[49m\u001b[43mfind\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 9\u001b[39m \u001b[43m \u001b[49m\u001b[43mcompany_id\u001b[49m\u001b[43m=\u001b[49m\u001b[43mcompany\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mid\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 10\u001b[39m \u001b[43m \u001b[49m\u001b[43muser_id\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mhfritz@r-o.com\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\n\u001b[32m 11\u001b[39m \u001b[43m)\u001b[49m\n\u001b[32m 12\u001b[39m \u001b[38;5;28mprint\u001b[39m(user_by_email)\n\u001b[32m 13\u001b[39m \u001b[38;5;66;03m# Find by name\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Projects/ProPyCore/ProPyCore/access/directory/users.py:144\u001b[39m, in \u001b[36mUsers.find\u001b[39m\u001b[34m(self, company_id, user_id, project_id)\u001b[39m\n\u001b[32m 141\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 142\u001b[39m key = \u001b[33m\"\u001b[39m\u001b[33mname\u001b[39m\u001b[33m\"\u001b[39m\n\u001b[32m--> \u001b[39m\u001b[32m144\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m user \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[43mcompany_id\u001b[49m\u001b[43m=\u001b[49m\u001b[43mcompany_id\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mproject_id\u001b[49m\u001b[43m=\u001b[49m\u001b[43mproject_id\u001b[49m\u001b[43m)\u001b[49m:\n\u001b[32m 145\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m user[key] == user_id:\n\u001b[32m 146\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m user\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Projects/ProPyCore/ProPyCore/access/directory/users.py:70\u001b[39m, in \u001b[36mUsers.get\u001b[39m\u001b[34m(self, company_id, project_id, per_page)\u001b[39m\n\u001b[32m 61\u001b[39m headers = {\n\u001b[32m 62\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mProcore-Company-Id\u001b[39m\u001b[33m\"\u001b[39m: \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mcompany_id\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 63\u001b[39m }\n\u001b[32m 65\u001b[39m url = \u001b[38;5;28mself\u001b[39m.get_url(\n\u001b[32m 66\u001b[39m company_id=company_id,\n\u001b[32m 67\u001b[39m project_id=project_id\n\u001b[32m 68\u001b[39m )\n\u001b[32m---> \u001b[39m\u001b[32m70\u001b[39m users_per_page = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mget_request\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 71\u001b[39m \u001b[43m \u001b[49m\u001b[43mapi_url\u001b[49m\u001b[43m=\u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 72\u001b[39m \u001b[43m \u001b[49m\u001b[43madditional_headers\u001b[49m\u001b[43m=\u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 73\u001b[39m \u001b[43m \u001b[49m\u001b[43mparams\u001b[49m\u001b[43m=\u001b[49m\u001b[43mparams\u001b[49m\n\u001b[32m 74\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 75\u001b[39m n_users = \u001b[38;5;28mlen\u001b[39m(users_per_page)\n\u001b[32m 77\u001b[39m users += users_per_page\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Projects/ProPyCore/ProPyCore/access/base.py:53\u001b[39m, in \u001b[36mBase.get_request\u001b[39m\u001b[34m(self, api_url, additional_headers, params)\u001b[39m\n\u001b[32m 50\u001b[39m \u001b[38;5;28;01mfor\u001b[39;00m key, value \u001b[38;5;129;01min\u001b[39;00m additional_headers.items():\n\u001b[32m 51\u001b[39m headers[key] = value\n\u001b[32m---> \u001b[39m\u001b[32m53\u001b[39m response = \u001b[43mrequests\u001b[49m\u001b[43m.\u001b[49m\u001b[43mget\u001b[49m\u001b[43m(\u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m=\u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 55\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m response.ok:\n\u001b[32m 56\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m response.json()\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Projects/ProPyCore/.venv/lib/python3.13/site-packages/requests/api.py:73\u001b[39m, in \u001b[36mget\u001b[39m\u001b[34m(url, params, **kwargs)\u001b[39m\n\u001b[32m 62\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34mget\u001b[39m(url, params=\u001b[38;5;28;01mNone\u001b[39;00m, **kwargs):\n\u001b[32m 63\u001b[39m \u001b[38;5;250m \u001b[39m\u001b[33mr\u001b[39m\u001b[33;03m\"\"\"Sends a GET request.\u001b[39;00m\n\u001b[32m 64\u001b[39m \n\u001b[32m 65\u001b[39m \u001b[33;03m :param url: URL for the new :class:`Request` object.\u001b[39;00m\n\u001b[32m (...)\u001b[39m\u001b[32m 70\u001b[39m \u001b[33;03m :rtype: requests.Response\u001b[39;00m\n\u001b[32m 71\u001b[39m \u001b[33;03m \"\"\"\u001b[39;00m\n\u001b[32m---> \u001b[39m\u001b[32m73\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mget\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mparams\u001b[49m\u001b[43m=\u001b[49m\u001b[43mparams\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Projects/ProPyCore/.venv/lib/python3.13/site-packages/requests/api.py:59\u001b[39m, in \u001b[36mrequest\u001b[39m\u001b[34m(method, url, **kwargs)\u001b[39m\n\u001b[32m 55\u001b[39m \u001b[38;5;66;03m# By using the 'with' statement we are sure the session is closed, thus we\u001b[39;00m\n\u001b[32m 56\u001b[39m \u001b[38;5;66;03m# avoid leaving sockets open which can trigger a ResourceWarning in some\u001b[39;00m\n\u001b[32m 57\u001b[39m \u001b[38;5;66;03m# cases, and look like a memory leak in others.\u001b[39;00m\n\u001b[32m 58\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m sessions.Session() \u001b[38;5;28;01mas\u001b[39;00m session:\n\u001b[32m---> \u001b[39m\u001b[32m59\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43msession\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m=\u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m=\u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Projects/ProPyCore/.venv/lib/python3.13/site-packages/requests/sessions.py:589\u001b[39m, in \u001b[36mSession.request\u001b[39m\u001b[34m(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)\u001b[39m\n\u001b[32m 584\u001b[39m send_kwargs = {\n\u001b[32m 585\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mtimeout\u001b[39m\u001b[33m\"\u001b[39m: timeout,\n\u001b[32m 586\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mallow_redirects\u001b[39m\u001b[33m\"\u001b[39m: allow_redirects,\n\u001b[32m 587\u001b[39m }\n\u001b[32m 588\u001b[39m send_kwargs.update(settings)\n\u001b[32m--> \u001b[39m\u001b[32m589\u001b[39m resp = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[43mprep\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43msend_kwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 591\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m resp\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Projects/ProPyCore/.venv/lib/python3.13/site-packages/requests/sessions.py:703\u001b[39m, in \u001b[36mSession.send\u001b[39m\u001b[34m(self, request, **kwargs)\u001b[39m\n\u001b[32m 700\u001b[39m start = preferred_clock()\n\u001b[32m 702\u001b[39m \u001b[38;5;66;03m# Send the request\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m703\u001b[39m r = \u001b[43madapter\u001b[49m\u001b[43m.\u001b[49m\u001b[43msend\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 705\u001b[39m \u001b[38;5;66;03m# Total elapsed time of the request (approximately)\u001b[39;00m\n\u001b[32m 706\u001b[39m elapsed = preferred_clock() - start\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Projects/ProPyCore/.venv/lib/python3.13/site-packages/requests/adapters.py:667\u001b[39m, in \u001b[36mHTTPAdapter.send\u001b[39m\u001b[34m(self, request, stream, timeout, verify, cert, proxies)\u001b[39m\n\u001b[32m 664\u001b[39m timeout = TimeoutSauce(connect=timeout, read=timeout)\n\u001b[32m 666\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m667\u001b[39m resp = \u001b[43mconn\u001b[49m\u001b[43m.\u001b[49m\u001b[43murlopen\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 668\u001b[39m \u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m=\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m.\u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 669\u001b[39m \u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m=\u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 670\u001b[39m \u001b[43m \u001b[49m\u001b[43mbody\u001b[49m\u001b[43m=\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbody\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 671\u001b[39m \u001b[43m \u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m=\u001b[49m\u001b[43mrequest\u001b[49m\u001b[43m.\u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 672\u001b[39m \u001b[43m \u001b[49m\u001b[43mredirect\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 673\u001b[39m \u001b[43m \u001b[49m\u001b[43massert_same_host\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 674\u001b[39m \u001b[43m \u001b[49m\u001b[43mpreload_content\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 675\u001b[39m \u001b[43m \u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 676\u001b[39m \u001b[43m \u001b[49m\u001b[43mretries\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mmax_retries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 677\u001b[39m \u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 678\u001b[39m \u001b[43m \u001b[49m\u001b[43mchunked\u001b[49m\u001b[43m=\u001b[49m\u001b[43mchunked\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 679\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 681\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m (ProtocolError, \u001b[38;5;167;01mOSError\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[32m 682\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mConnectionError\u001b[39;00m(err, request=request)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Projects/ProPyCore/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:787\u001b[39m, in \u001b[36mHTTPConnectionPool.urlopen\u001b[39m\u001b[34m(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)\u001b[39m\n\u001b[32m 784\u001b[39m response_conn = conn \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m release_conn \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 786\u001b[39m \u001b[38;5;66;03m# Make the request on the HTTPConnection object\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m787\u001b[39m response = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_make_request\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 788\u001b[39m \u001b[43m \u001b[49m\u001b[43mconn\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 789\u001b[39m \u001b[43m \u001b[49m\u001b[43mmethod\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 790\u001b[39m \u001b[43m \u001b[49m\u001b[43murl\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 791\u001b[39m \u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtimeout_obj\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 792\u001b[39m \u001b[43m \u001b[49m\u001b[43mbody\u001b[49m\u001b[43m=\u001b[49m\u001b[43mbody\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 793\u001b[39m \u001b[43m \u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m=\u001b[49m\u001b[43mheaders\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 794\u001b[39m \u001b[43m \u001b[49m\u001b[43mchunked\u001b[49m\u001b[43m=\u001b[49m\u001b[43mchunked\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 795\u001b[39m \u001b[43m \u001b[49m\u001b[43mretries\u001b[49m\u001b[43m=\u001b[49m\u001b[43mretries\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 796\u001b[39m \u001b[43m \u001b[49m\u001b[43mresponse_conn\u001b[49m\u001b[43m=\u001b[49m\u001b[43mresponse_conn\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 797\u001b[39m \u001b[43m \u001b[49m\u001b[43mpreload_content\u001b[49m\u001b[43m=\u001b[49m\u001b[43mpreload_content\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 798\u001b[39m \u001b[43m \u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[43m=\u001b[49m\u001b[43mdecode_content\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 799\u001b[39m \u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mresponse_kw\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 800\u001b[39m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 802\u001b[39m \u001b[38;5;66;03m# Everything went great!\u001b[39;00m\n\u001b[32m 803\u001b[39m clean_exit = \u001b[38;5;28;01mTrue\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Projects/ProPyCore/.venv/lib/python3.13/site-packages/urllib3/connectionpool.py:534\u001b[39m, in \u001b[36mHTTPConnectionPool._make_request\u001b[39m\u001b[34m(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)\u001b[39m\n\u001b[32m 532\u001b[39m \u001b[38;5;66;03m# Receive the response from the server\u001b[39;00m\n\u001b[32m 533\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m534\u001b[39m response = \u001b[43mconn\u001b[49m\u001b[43m.\u001b[49m\u001b[43mgetresponse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 535\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m (BaseSSLError, \u001b[38;5;167;01mOSError\u001b[39;00m) \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[32m 536\u001b[39m \u001b[38;5;28mself\u001b[39m._raise_timeout(err=e, url=url, timeout_value=read_timeout)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/Projects/ProPyCore/.venv/lib/python3.13/site-packages/urllib3/connection.py:516\u001b[39m, in \u001b[36mHTTPConnection.getresponse\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 513\u001b[39m _shutdown = \u001b[38;5;28mgetattr\u001b[39m(\u001b[38;5;28mself\u001b[39m.sock, \u001b[33m\"\u001b[39m\u001b[33mshutdown\u001b[39m\u001b[33m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m)\n\u001b[32m 515\u001b[39m \u001b[38;5;66;03m# Get the response from http.client.HTTPConnection\u001b[39;00m\n\u001b[32m--> \u001b[39m\u001b[32m516\u001b[39m httplib_response = \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m.\u001b[49m\u001b[43mgetresponse\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 518\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 519\u001b[39m assert_header_parsing(httplib_response.msg)\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Cellar/python@3.13/3.13.3/Frameworks/Python.framework/Versions/3.13/lib/python3.13/http/client.py:1430\u001b[39m, in \u001b[36mHTTPConnection.getresponse\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 1428\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 1429\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1430\u001b[39m \u001b[43mresponse\u001b[49m\u001b[43m.\u001b[49m\u001b[43mbegin\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1431\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mConnectionError\u001b[39;00m:\n\u001b[32m 1432\u001b[39m \u001b[38;5;28mself\u001b[39m.close()\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Cellar/python@3.13/3.13.3/Frameworks/Python.framework/Versions/3.13/lib/python3.13/http/client.py:331\u001b[39m, in \u001b[36mHTTPResponse.begin\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 329\u001b[39m \u001b[38;5;66;03m# read until we get a non-100 response\u001b[39;00m\n\u001b[32m 330\u001b[39m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m331\u001b[39m version, status, reason = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_read_status\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 332\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m status != CONTINUE:\n\u001b[32m 333\u001b[39m \u001b[38;5;28;01mbreak\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Cellar/python@3.13/3.13.3/Frameworks/Python.framework/Versions/3.13/lib/python3.13/http/client.py:292\u001b[39m, in \u001b[36mHTTPResponse._read_status\u001b[39m\u001b[34m(self)\u001b[39m\n\u001b[32m 291\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m_read_status\u001b[39m(\u001b[38;5;28mself\u001b[39m):\n\u001b[32m--> \u001b[39m\u001b[32m292\u001b[39m line = \u001b[38;5;28mstr\u001b[39m(\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mfp\u001b[49m\u001b[43m.\u001b[49m\u001b[43mreadline\u001b[49m\u001b[43m(\u001b[49m\u001b[43m_MAXLINE\u001b[49m\u001b[43m \u001b[49m\u001b[43m+\u001b[49m\u001b[43m \u001b[49m\u001b[32;43m1\u001b[39;49m\u001b[43m)\u001b[49m, \u001b[33m\"\u001b[39m\u001b[33miso-8859-1\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 293\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mlen\u001b[39m(line) > _MAXLINE:\n\u001b[32m 294\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m LineTooLong(\u001b[33m\"\u001b[39m\u001b[33mstatus line\u001b[39m\u001b[33m\"\u001b[39m)\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Cellar/python@3.13/3.13.3/Frameworks/Python.framework/Versions/3.13/lib/python3.13/socket.py:719\u001b[39m, in \u001b[36mSocketIO.readinto\u001b[39m\u001b[34m(self, b)\u001b[39m\n\u001b[32m 717\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mOSError\u001b[39;00m(\u001b[33m\"\u001b[39m\u001b[33mcannot read from timed out object\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m 718\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m719\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_sock\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrecv_into\u001b[49m\u001b[43m(\u001b[49m\u001b[43mb\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 720\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m timeout:\n\u001b[32m 721\u001b[39m \u001b[38;5;28mself\u001b[39m._timeout_occurred = \u001b[38;5;28;01mTrue\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Cellar/python@3.13/3.13.3/Frameworks/Python.framework/Versions/3.13/lib/python3.13/ssl.py:1304\u001b[39m, in \u001b[36mSSLSocket.recv_into\u001b[39m\u001b[34m(self, buffer, nbytes, flags)\u001b[39m\n\u001b[32m 1300\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m flags != \u001b[32m0\u001b[39m:\n\u001b[32m 1301\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[32m 1302\u001b[39m \u001b[33m\"\u001b[39m\u001b[33mnon-zero flags not allowed in calls to recv_into() on \u001b[39m\u001b[38;5;132;01m%s\u001b[39;00m\u001b[33m\"\u001b[39m %\n\u001b[32m 1303\u001b[39m \u001b[38;5;28mself\u001b[39m.\u001b[34m__class__\u001b[39m)\n\u001b[32m-> \u001b[39m\u001b[32m1304\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnbytes\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbuffer\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1305\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1306\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28msuper\u001b[39m().recv_into(buffer, nbytes, flags)\n", + "\u001b[36mFile \u001b[39m\u001b[32m/opt/homebrew/Cellar/python@3.13/3.13.3/Frameworks/Python.framework/Versions/3.13/lib/python3.13/ssl.py:1138\u001b[39m, in \u001b[36mSSLSocket.read\u001b[39m\u001b[34m(self, len, buffer)\u001b[39m\n\u001b[32m 1136\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 1137\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m buffer \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1138\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_sslobj\u001b[49m\u001b[43m.\u001b[49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mlen\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mbuffer\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1139\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1140\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m._sslobj.read(\u001b[38;5;28mlen\u001b[39m)\n", + "\u001b[31mKeyboardInterrupt\u001b[39m: " + ] + } + ], "source": [ "# Find by id\n", "user_by_id = connection.directory.users.find(\n", @@ -338,6 +515,562 @@ " * list where each value is a dict with a vendors's information" ] }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of vendors: 4967\n", + "{\n", + " \"id\": 5181441,\n", + " \"abbreviated_name\": \"\",\n", + " \"address\": \"221 WEST 6TH STREET, SUITE 1800\",\n", + " \"attachments\": [],\n", + " \"authorized_bidder\": true,\n", + " \"bidding\": {\n", + " \"affirmative_action\": false,\n", + " \"african_american_business\": false,\n", + " \"asian_american_business\": false,\n", + " \"certified_business_enterprise\": false,\n", + " \"disadvantaged_business\": false,\n", + " \"eight_a_business\": false,\n", + " \"hispanic_business\": false,\n", + " \"historically_underutilized_business\": false,\n", + " \"minority_business_enterprise\": false,\n", + " \"native_american_business\": false,\n", + " \"sdvo_business\": false,\n", + " \"small_business\": false,\n", + " \"womens_business\": false\n", + " },\n", + " \"bidding_distribution\": [],\n", + " \"business_id\": null,\n", + " \"business_phone\": \"5121234567\",\n", + " \"business_register\": null,\n", + " \"children_count\": 0,\n", + " \"city\": \"Austin\",\n", + " \"company\": \"Procore (Test Companies)\",\n", + " \"company_vendor\": false,\n", + " \"connected_to_company_id\": null,\n", + " \"contact_count\": 9,\n", + " \"country_code\": \"US\",\n", + " \"created_at\": \"2017-03-01T12:00:10Z\",\n", + " \"email_address\": \"\",\n", + " \"fax_number\": \"\",\n", + " \"is_active\": true,\n", + " \"is_connected\": false,\n", + " \"labor_union\": \"\",\n", + " \"legal_name\": \"Procore (Test Companies)\",\n", + " \"license_number\": \"\",\n", + " \"logo\": null,\n", + " \"mobile_phone\": null,\n", + " \"name\": \"Procore (Test Companies)\",\n", + " \"non_union_prevailing_wage\": false,\n", + " \"notes\": \"\",\n", + " \"origin_code\": null,\n", + " \"origin_data\": null,\n", + " \"origin_id\": null,\n", + " \"parent\": null,\n", + " \"prequalified\": false,\n", + " \"primary_contact\": {\n", + " \"id\": 1344388,\n", + " \"business_phone\": \"\",\n", + " \"business_phone_extension\": null,\n", + " \"created_at\": \"2020-04-06T18:53:40Z\",\n", + " \"email_address\": \"katrina.schlosser+demo@procore.com\",\n", + " \"fax_number\": \"\",\n", + " \"first_name\": \"Katrina\",\n", + " \"last_name\": \"Demo\",\n", + " \"mobile_phone\": \"\",\n", + " \"updated_at\": \"2025-02-24T20:10:12Z\"\n", + " },\n", + " \"project_ids\": [\n", + " 291567,\n", + " 1229394,\n", + " 1258045,\n", + " 1265287,\n", + " 1277026,\n", + " 1281887,\n", + " 1293500,\n", + " 1320486,\n", + " 1328928,\n", + " 1337442,\n", + " 1344275,\n", + " 1344292,\n", + " 1344849,\n", + " 1363599,\n", + " 1363606,\n", + " 1363608,\n", + " 1364990,\n", + " 1374233,\n", + " 1374238,\n", + " 1374244,\n", + " 1374251,\n", + " 1374255,\n", + " 1374261,\n", + " 1374282,\n", + " 1374285,\n", + " 1374288,\n", + " 1374292,\n", + " 1374295,\n", + " 1374313,\n", + " 1377983,\n", + " 1378127,\n", + " 1378131,\n", + " 1378137,\n", + " 1378141,\n", + " 1378146,\n", + " 1378148,\n", + " 1378151,\n", + " 1378156,\n", + " 1378161,\n", + " 1378164,\n", + " 1378166,\n", + " 1378171,\n", + " 1378176,\n", + " 1378182,\n", + " 1378184,\n", + " 1378188,\n", + " 1378196,\n", + " 1378199,\n", + " 1378202,\n", + " 1378206,\n", + " 1378208,\n", + " 1378211,\n", + " 1378215,\n", + " 1378219,\n", + " 1378223,\n", + " 1378227,\n", + " 1378229,\n", + " 1378234,\n", + " 1380378,\n", + " 1380882,\n", + " 1382096,\n", + " 1389620,\n", + " 1389629,\n", + " 1389648,\n", + " 1389654,\n", + " 1391642,\n", + " 1411794,\n", + " 1412730,\n", + " 1435623,\n", + " 1435641,\n", + " 1435805,\n", + " 1435819,\n", + " 1445182,\n", + " 1446630,\n", + " 1448163,\n", + " 1451882,\n", + " 1451914,\n", + " 1453323,\n", + " 1456385,\n", + " 1461356,\n", + " 1461398,\n", + " 1471966,\n", + " 1487934,\n", + " 1494731,\n", + " 1496498,\n", + " 1496505,\n", + " 1496509,\n", + " 1496511,\n", + " 1496516,\n", + " 1497863,\n", + " 1499800,\n", + " 1500407,\n", + " 1505890,\n", + " 1513228,\n", + " 1517456,\n", + " 1517461,\n", + " 1518166,\n", + " 1530234,\n", + " 1530245,\n", + " 1541035,\n", + " 1548154,\n", + " 1551952,\n", + " 1559408,\n", + " 1572860,\n", + " 1572914,\n", + " 1573127,\n", + " 1573128,\n", + " 1573129,\n", + " 1573130,\n", + " 1594687,\n", + " 1632011,\n", + " 1635021,\n", + " 1640387,\n", + " 1640434,\n", + " 1642207,\n", + " 1642294,\n", + " 1642712,\n", + " 1647562,\n", + " 1653151,\n", + " 1658522,\n", + " 1658977,\n", + " 1663925,\n", + " 1668030,\n", + " 1670899,\n", + " 1683993,\n", + " 1683996,\n", + " 1714263,\n", + " 1734487,\n", + " 1748864,\n", + " 1756058,\n", + " 1770790,\n", + " 1775556,\n", + " 1787461,\n", + " 1799330,\n", + " 1809568,\n", + " 1812475,\n", + " 1824100,\n", + " 1838686,\n", + " 1842970,\n", + " 1857307,\n", + " 1857308,\n", + " 1857309,\n", + " 1868560,\n", + " 1868581,\n", + " 1874224,\n", + " 1883739,\n", + " 1885754,\n", + " 1918069,\n", + " 1918092,\n", + " 1918103,\n", + " 1932379,\n", + " 1949421,\n", + " 1949456,\n", + " 1957277,\n", + " 1994573,\n", + " 1994592,\n", + " 1994595,\n", + " 1994604,\n", + " 1994605,\n", + " 1994609,\n", + " 1994611,\n", + " 1994612,\n", + " 2001181,\n", + " 2023709,\n", + " 2023717,\n", + " 2023720,\n", + " 2023728,\n", + " 2023731,\n", + " 2023951,\n", + " 2029556,\n", + " 2041304,\n", + " 2070047,\n", + " 2075854,\n", + " 2075909,\n", + " 2079103,\n", + " 2083435,\n", + " 2083436,\n", + " 2088217,\n", + " 2093902,\n", + " 2097047,\n", + " 2104854,\n", + " 2104856,\n", + " 2110665,\n", + " 2115769,\n", + " 2117835,\n", + " 2121003,\n", + " 2126893,\n", + " 2126895,\n", + " 2126896,\n", + " 2126897,\n", + " 2126898,\n", + " 2126899,\n", + " 2132077,\n", + " 2135329,\n", + " 2136703,\n", + " 2136722,\n", + " 2136723,\n", + " 2136724,\n", + " 2136725,\n", + " 2136726,\n", + " 2144448,\n", + " 2156208,\n", + " 2176966,\n", + " 2192905,\n", + " 2192906,\n", + " 2213693,\n", + " 2220098,\n", + " 2220990,\n", + " 2221007,\n", + " 2225333,\n", + " 2226689,\n", + " 2226690,\n", + " 2232254,\n", + " 2232281,\n", + " 2235500,\n", + " 2235518,\n", + " 2238254,\n", + " 2246895,\n", + " 2246943,\n", + " 2254655,\n", + " 2257426,\n", + " 2257459,\n", + " 2261578,\n", + " 2271123,\n", + " 2281708,\n", + " 2286621,\n", + " 2289314,\n", + " 2300539,\n", + " 2302947,\n", + " 2306115,\n", + " 2319467,\n", + " 2319575,\n", + " 2325244,\n", + " 2329429,\n", + " 2332242,\n", + " 2332282,\n", + " 2340130,\n", + " 2340131,\n", + " 2340132,\n", + " 2340133,\n", + " 2344827,\n", + " 2359603,\n", + " 2365121,\n", + " 2374377,\n", + " 2394446,\n", + " 2394447,\n", + " 2394536,\n", + " 2402603,\n", + " 2402669,\n", + " 2412567,\n", + " 2416077,\n", + " 2424954,\n", + " 2426790,\n", + " 2426831,\n", + " 2426879,\n", + " 2430171,\n", + " 2430207,\n", + " 2435865,\n", + " 2437249,\n", + " 2437343,\n", + " 2469297,\n", + " 2469340,\n", + " 2469756,\n", + " 2469772,\n", + " 2473706,\n", + " 2473714,\n", + " 2499097,\n", + " 2500594,\n", + " 2505644,\n", + " 2507742,\n", + " 2508682,\n", + " 2512856,\n", + " 2513973,\n", + " 2518161,\n", + " 2520098,\n", + " 2521636,\n", + " 2523491,\n", + " 2523788,\n", + " 2525416,\n", + " 2527840,\n", + " 2527855,\n", + " 2533102,\n", + " 2539684,\n", + " 2539693,\n", + " 2543854,\n", + " 2545130,\n", + " 2553102,\n", + " 2553113,\n", + " 2555161,\n", + " 2559684,\n", + " 2563500,\n", + " 2563853,\n", + " 2563870,\n", + " 2563874,\n", + " 2563880,\n", + " 2569981,\n", + " 2574734,\n", + " 2577234,\n", + " 2583836,\n", + " 2586287,\n", + " 2586597,\n", + " 2587091,\n", + " 2591748,\n", + " 2597749,\n", + " 2597765,\n", + " 2602910,\n", + " 2604097,\n", + " 2604111,\n", + " 2605193,\n", + " 2605726,\n", + " 2612115,\n", + " 2615208,\n", + " 2624891,\n", + " 2625824,\n", + " 2630457,\n", + " 2631148,\n", + " 2632886,\n", + " 2645343,\n", + " 2646312,\n", + " 2648914,\n", + " 2649261,\n", + " 2652233,\n", + " 2653760,\n", + " 2657401,\n", + " 2657733,\n", + " 2659540,\n", + " 2663087,\n", + " 2668389,\n", + " 2669576,\n", + " 2681037,\n", + " 2681042,\n", + " 2685091,\n", + " 2685112,\n", + " 2685462,\n", + " 2689348,\n", + " 2692387,\n", + " 2696785,\n", + " 2703047,\n", + " 2703060,\n", + " 2703935,\n", + " 2705017,\n", + " 2715186,\n", + " 2715204,\n", + " 2715210,\n", + " 2715215,\n", + " 2715221,\n", + " 2722159,\n", + " 2735855,\n", + " 2778788,\n", + " 2783683,\n", + " 2850670,\n", + " 2850724,\n", + " 2850780,\n", + " 2850796,\n", + " 2868797,\n", + " 2872850,\n", + " 2894414,\n", + " 2919601,\n", + " 2963583,\n", + " 2990299,\n", + " 3007026,\n", + " 3041908\n", + " ],\n", + " \"standard_cost_codes\": [],\n", + " \"state_code\": \"TX\",\n", + " \"synced_to_erp\": false,\n", + " \"trade_name\": \"\",\n", + " \"trades\": [\n", + " {\n", + " \"id\": 187607,\n", + " \"active\": true,\n", + " \"name\": \"Concrete\",\n", + " \"updated_at\": \"2017-05-11T21:25:42Z\"\n", + " }\n", + " ],\n", + " \"union_member\": false,\n", + " \"updated_at\": \"2025-04-11T15:15:02Z\",\n", + " \"vendor_group\": null,\n", + " \"website\": \"\",\n", + " \"zip\": \"78701\"\n", + "}\n" + ] + } + ], + "source": [ + "comp_vendors = connection.directory.vendors.get(\n", + " company_id=company[\"id\"]\n", + ")\n", + "\n", + "print(\"Number of vendors:\", len(comp_vendors))\n", + "print(json.dumps(comp_vendors[0],indent=4))" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of vendors: 7\n", + "{\n", + " \"id\": 5181441,\n", + " \"abbreviated_name\": \"\",\n", + " \"address\": \"221 WEST 6TH STREET, SUITE 1800\",\n", + " \"attachments\": [],\n", + " \"authorized_bidder\": true,\n", + " \"business_id\": null,\n", + " \"business_phone\": \"5121234567\",\n", + " \"business_register\": null,\n", + " \"city\": \"Austin\",\n", + " \"company\": \"Procore (Test Companies)\",\n", + " \"company_vendor\": false,\n", + " \"country_code\": \"US\",\n", + " \"created_at\": \"2017-03-01T12:00:10Z\",\n", + " \"email_address\": \"\",\n", + " \"fax_number\": \"\",\n", + " \"is_active\": true,\n", + " \"is_connected\": false,\n", + " \"labor_union\": \"\",\n", + " \"license_number\": \"\",\n", + " \"logo\": null,\n", + " \"mobile_phone\": null,\n", + " \"name\": \"Procore (Test Companies)\",\n", + " \"non_union_prevailing_wage\": false,\n", + " \"notes\": \"\",\n", + " \"origin_code\": null,\n", + " \"origin_data\": null,\n", + " \"origin_id\": null,\n", + " \"prequalified\": false,\n", + " \"primary_contact\": {\n", + " \"id\": 1344388,\n", + " \"business_phone\": \"\",\n", + " \"business_phone_extension\": null,\n", + " \"created_at\": \"2020-04-06T18:53:40Z\",\n", + " \"email_address\": \"katrina.schlosser+demo@procore.com\",\n", + " \"fax_number\": \"\",\n", + " \"first_name\": \"Katrina\",\n", + " \"last_name\": \"Demo\",\n", + " \"mobile_phone\": \"\",\n", + " \"updated_at\": \"2025-02-24T20:10:12Z\"\n", + " },\n", + " \"project_ids\": [\n", + " 1494731,\n", + " 1668030,\n", + " 1949421,\n", + " 2041304,\n", + " 2121003,\n", + " 2261578,\n", + " 2289314,\n", + " 2394447,\n", + " 2430207,\n", + " 2507742,\n", + " 2512856,\n", + " 2555161,\n", + " 2574734,\n", + " 2602910,\n", + " 2783683\n", + " ],\n", + " \"state_code\": \"TX\",\n", + " \"synced_to_erp\": false,\n", + " \"trade_name\": \"\",\n", + " \"union_member\": false,\n", + " \"updated_at\": \"2025-04-11T15:15:02Z\",\n", + " \"vendor_group\": null,\n", + " \"website\": \"\",\n", + " \"zip\": \"78701\"\n", + "}\n" + ] + } + ], + "source": [ + "proj_vendors = connection.directory.vendors.get(\n", + " company_id=company[\"id\"],\n", + " project_id=project[\"id\"]\n", + ")\n", + "\n", + "print(\"Number of vendors:\", len(proj_vendors))\n", + "print(json.dumps(proj_vendors[0],indent=4))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -359,6 +1092,143 @@ " * project-specific dictionary" ] }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of vendors: 108\n", + "Procore (Test Companies)\n", + "Subcontracting TEST Company\n", + "L.C.R. Contractors, LLC\n", + "Airco Mechanical, Ltd\n", + "SteelFab Texas, Inc\n", + "Galindo & Boyd Wall Systems, LLC\n", + "07 Specialties\n", + "TK Elevator Corporation\n", + "Texas Commercial Glass Concepts, LP\n", + "Spectrum Resource Group Ltd\n", + "Greater Metroplex Interiors, Inc.\n", + "ThyssenKrupp Elevator\n", + "Ranger Excavating, LP\n", + "Capitol Concrete Contractors Inc\n", + "Austin ISD\n", + "Power Design, Inc\n", + "Chamberlin Austin LLC\n", + "Abatix\n", + "Cardinal Strategies Environmental Services, LLC\n", + "Johnson Equipment Co.\n", + "PSG\n", + "Ram Tool & Supply\n", + "Ultimate Crane Services\n", + "Datamax\n", + "Viking Fence Co., Ltd\n", + "Contractors Access Equipment\n", + "P&J Arcomet Services LLC\n", + "Thyssen-Krupp Elevator Corp-Te\n", + "Oscar Orduno Inc\n", + "Ace Contractors Supply\n", + "Overhead Door Co., Austin\n", + "Recon Services, Inc.\n", + "Sunbelt Rentals, Inc\n", + "Superior Septic/Clean Can\n", + "Centex Personnel Services LLC\n", + "Anytime Fuel Pros LLC\n", + "5 Star Fabrications Inc\n", + "TRP Construction Group, LLC\n", + "Kennedy Wire Rope and Sling Company Inc.\n", + "4Ward Land Surveying, LLC\n", + "RGS Products, Inc.\n", + "Spectrum Resource SCD, LLC\n", + "Staff Zone\n", + "Landmark Surveying L.P.\n", + "Precision Commercial Plumbing Co., Inc.\n", + "ThyssenKrup Elevator Corp. Aus\n", + "Environmental Tree & Design, Inc\n", + "Texas One Security Services, LLC\n", + "Armetco Systems, LLC\n", + "Haegelin Wehmeyer Construction Ltd.\n", + "Pro-Bel Enterprises Limited\n", + "Rogers-O'Brien (Equipment)\n", + "Bryer Construction and Installation, LLC\n", + "Koetter Fire Protection of Austin, LLC\n", + "Penhall Company\n", + "Escobedo Cranes & Equipment LP\n", + "Rogers-O'Brien Construction Company\n", + "Kidd Roofing\n", + "ModernCrete Concrete Design\n", + "Structural Technologies LLC\n", + "BLW Place and Finish, LLC\n", + "CMC Commercial Metals\n", + "STG Design, Inc.\n", + "Viewtech\n", + "The Davey Tree Expert Company\n", + "Building Abatement Demolition Co\n", + "Texas Fifth Wall Roofing Systems, Inc.\n", + "Keller North America, Inc.\n", + "D&W Painting Inc.\n", + "Slater Painting Company, Inc.\n", + "CMC Rebar\n", + "Probel\n", + "TruTeam Builder Services Group, Inc dba LCR Contractors\n", + "The Door Frame & Hardware Company - Austin, LLC\n", + "BrightView Landscape Development, Inc.\n", + "Tecta America Austin, LLC\n", + "Metalink, LLC\n", + "United Forming, Inc.\n", + "Gulf Coast Pavers, Inc\n", + "Harris Rebar Nufab LLC\n", + "LOC Consultants Civil Division, Inc\n", + "McClone Construction\n", + "Orco Steel LLC\n", + "Perry & Perry Builders\n", + "Southwest Progressive Enterprises\n", + "Stripe It Up, LLC\n", + "Stone Panels International, LLC\n", + "Texas Concrete\n", + "Bay and Associates, Inc.\n", + "Denbow Company, Inc\n", + "Texas Miscellaneous Iron, Inc\n", + "McDonnel Consulting\n", + "Uperio Services LLC\n", + "Tri-C Contracting, LLC\n", + "GBC General Barricade Company, LLC\n", + "Total Floors\n", + "BZ Post-Tension, LLC\n", + "Construction Specialties, Inc.\n", + "Paint Test Company\n", + "Henley-Johnston & Associates, Inc.\n", + "Rolling Plains Construction, Inc\n", + "Seamless SOLA I, LP\n", + "Radius Security U.S. Corporation\n", + "Janus International Group, LLC\n", + "Marx Okubo\n", + "Illuminatilabs, LLC\n", + "Environmental Allies\n", + "Steel House MFG\n" + ] + } + ], + "source": [ + "project2 = connection.projects.find(\n", + " company_id=company[\"id\"],\n", + " identifier=1668030\n", + ")\n", + "\n", + "proj_vendors2 = connection.directory.vendors.get(\n", + " company_id=company[\"id\"],\n", + " project_id=project2[\"id\"]\n", + ")\n", + "\n", + "print(\"Number of vendors:\", len(proj_vendors2))\n", + "for vendor in proj_vendors2:\n", + " print(vendor[\"company\"])" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -378,7 +1248,15 @@ "cell_type": "code", "execution_count": null, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'id': 25138567, 'first_name': 'Hagen', 'last_name': 'Fritz', 'employee_id': '11986', 'work_classification_id': None, 'is_employee': True, 'user_id': 8780450, 'origin_id': None, 'contact_id': 25212255, 'contact': {'is_active': True, 'email': 'hfritz@r-o.com'}, 'name': 'Hagen Fritz', 'user_uuid': 'a680cd90-7e33-4f86-8375-8eaf72f48b04'}\n" + ] + } + ], "source": [ "# Find by email\n", "person_by_email = connection.directory.people.find(\n", @@ -398,7 +1276,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -412,7 +1290,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.13.3" } }, "nbformat": 4, diff --git a/snippets/find_user.py b/snippets/find_user.py deleted file mode 100644 index d396709..0000000 --- a/snippets/find_user.py +++ /dev/null @@ -1,55 +0,0 @@ -import os -import sys -import pathlib -sys.path.append(f"{pathlib.Path(__file__).resolve().parent.parent}") - -from ProPyCore.procore import Procore - -from dotenv import load_dotenv -import json - -if os.getenv("CLIENT_ID") is None: - load_dotenv() - -if __name__ == "__main__": - connection = Procore( - client_id=os.getenv("CLIENT_ID"), - client_secret=os.getenv("CLIENT_SECRET"), - redirect_uri=os.getenv("REDIRECT_URI"), - oauth_url=os.getenv("OAUTH_URL"), - base_url=os.getenv("BASE_URL") - ) - - company = connection.companies.find(identifier="Rogers-O`Brien Construction") - - # Example 1: Get company-level user - # --------- - print("Example 1") - comp_user = connection.directory.users.find( - company_id=company["id"], - user_id="Hagen Fritz" - ) - - print(f"{comp_user['id']}: {comp_user['name']}") - print(json.dumps(comp_user,indent=4)) - # See example in /references/ - - # Example 2: Get project-level users - # --------- - print("\nExample 2") - # find project - project = connection.projects.find( - company_id=company["id"], - identifier="Sandbox Test Project" - ) - - proj_user = connection.directory.users.find( - company_id=company["id"], - project_id=project["id"], - user_id=8780450 - ) - - print(f"{proj_user['id']}: {proj_user['name']}") - print(json.dumps(proj_user,indent=4)) - # See example in /references/ - diff --git a/snippets/get_users.py b/snippets/get_users.py deleted file mode 100644 index 6c08cf2..0000000 --- a/snippets/get_users.py +++ /dev/null @@ -1,54 +0,0 @@ -import os -import sys -import pathlib -sys.path.append(f"{pathlib.Path(__file__).resolve().parent.parent}") - -from propycore.procore import Procore -from propycore.exceptions import NotFoundItemError - -from dotenv import load_dotenv -import json - -if os.getenv("CLIENT_ID") is None: - load_dotenv() - -if __name__ == "__main__": - connection = Procore( - client_id=os.getenv("CLIENT_ID"), - client_secret=os.getenv("CLIENT_SECRET"), - redirect_uri=os.getenv("REDIRECT_URI"), - oauth_url=os.getenv("OAUTH_URL"), - base_url=os.getenv("BASE_URL") - ) - - company = connection.__companies__.find(identifier="Rogers-O`Brien Construction") - - # Example 1: Get company-level vendors - # --------- - print("Example 1") - comp_users = connection.__users__.get( - company_id=company["id"] - ) - - print("Number of users:", len(comp_users)) - print(json.dumps(comp_users[0],indent=4)) - # See example in /references/ - - # Example 2: Get project-level users - # --------- - print("\nExample 2") - # find project - project = connection.__projects__.find( - company_id=company["id"], - identifier="Sandbox Test Project" - ) - - proj_users = connection.__users__.get( - company_id=company["id"], - project_id=project["id"] - ) - - print("Number of users:", len(proj_users)) - print(json.dumps(proj_users[0],indent=4)) - # See example in /references/ - diff --git a/snippets/get_vendors.py b/snippets/get_vendors.py deleted file mode 100644 index 81f9611..0000000 --- a/snippets/get_vendors.py +++ /dev/null @@ -1,71 +0,0 @@ -import os -import sys -import pathlib -sys.path.append(f"{pathlib.Path(__file__).resolve().parent.parent}") - -from ProPyCore.procore import Procore - -from dotenv import load_dotenv -import json - -if os.getenv("CLIENT_ID") is None: - load_dotenv() - -if __name__ == "__main__": - connection = Procore( - client_id=os.getenv("CLIENT_ID"), - client_secret=os.getenv("CLIENT_SECRET"), - redirect_uri=os.getenv("REDIRECT_URI"), - oauth_url=os.getenv("OAUTH_URL"), - base_url=os.getenv("BASE_URL") - ) - - company = connection.companies.find(identifier="Rogers-O`Brien Construction") - - # Example 1: Get company-level users - # --------- - print("Example 1") - comp_vendors = connection.directory.vendors.get( - company_id=company["id"] - ) - - print("Number of vendors:", len(comp_vendors)) - print(json.dumps(comp_vendors[0],indent=4)) - # See example in /references/ - - # Example 2: Get project-level users - # --------- - print("\nExample 2") - # find project - project = connection.projects.find( - company_id=company["id"], - identifier="Sandbox Test Project" - ) - - proj_vendors = connection.directory.vendors.get( - company_id=company["id"], - project_id=project["id"] - ) - - print("Number of vendors:", len(proj_vendors)) - print(json.dumps(proj_vendors[0],indent=4)) - # See example in /references/ - - # Example 3: List all project vendor names - # --------- - print("\nExample 3") - # find project - project2 = connection.projects.find( - company_id=company["id"], - identifier=1668030 - ) - - proj_vendors2 = connection.directory.vendors.get( - company_id=company["id"], - project_id=project2["id"] - ) - - print("Number of vendors:", len(proj_vendors2)) - for vendor in proj_vendors2: - print(vendor["company"]) - diff --git a/snippets/trades.py b/snippets/trades.py deleted file mode 100644 index 219e986..0000000 --- a/snippets/trades.py +++ /dev/null @@ -1,48 +0,0 @@ -import os -import sys -import pathlib -sys.path.append(f"{pathlib.Path(__file__).resolve().parent.parent}") - -from propycore.procore import Procore -from propycore.exceptions import NotFoundItemError - -from dotenv import load_dotenv -import json - -if os.getenv("CLIENT_ID") is None: - load_dotenv() - -if __name__ == "__main__": - connection = Procore( - client_id=os.getenv("CLIENT_ID"), - client_secret=os.getenv("CLIENT_SECRET"), - redirect_uri=os.getenv("REDIRECT_URI"), - oauth_url=os.getenv("OAUTH_URL"), - base_url=os.getenv("BASE_URL") - ) - - company = connection.__companies__.find(identifier="Rogers-O`Brien Construction") - - # Example 1: Get trades - # --------- - print("Example 1") - trades = connection.__trades__.get(company_id=company["id"]) - - print("Number of trades:", len(trades)) - for trade in trades: - print(trade["name"]) # limited number so we might as well print them out - print(json.dumps(trades[0],indent=4)) - # See example in /references/ - - # Example 2: Find trades - # --------- - print("\nExample 2") - - trade = connection.__trades__.find( - company_id=company["id"], - user_id="Acoustical Ceilings" - ) - - print(f"{trade['id']}: {trade['name']}") - print(json.dumps(trade,indent=4)) - # See example in /references/ \ No newline at end of file From 10974f6e6ef2fd7381a5b39dc6ab0d0321505a5f Mon Sep 17 00:00:00 2001 From: Hagen Fritz Date: Wed, 28 May 2025 13:25:48 -0500 Subject: [PATCH 2/3] feat: get token refresh process to work --- ProPyCore/__init__.py | 2 +- ProPyCore/access/directory/trades.py | 6 ++++-- ProPyCore/procore.py | 4 ++++ setup.py | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ProPyCore/__init__.py b/ProPyCore/__init__.py index ca21a6c..677bdf4 100644 --- a/ProPyCore/__init__.py +++ b/ProPyCore/__init__.py @@ -3,4 +3,4 @@ from .exceptions import * from .procore import Procore -__version__ = "0.6.3" \ No newline at end of file +__version__ = "0.6.4" \ No newline at end of file diff --git a/ProPyCore/access/directory/trades.py b/ProPyCore/access/directory/trades.py index 1df70be..25e5779 100644 --- a/ProPyCore/access/directory/trades.py +++ b/ProPyCore/access/directory/trades.py @@ -39,8 +39,9 @@ def get(self, company_id, per_page=1000): "Procore-Company-Id": f"{company_id}" } + url = f"{self.endpoint}/{company_id}/trades" trades_per_page = self.get_request( - api_url=f"{self.endpoint}/{company_id}/trades", + api_url=url, additional_headers=headers, params=params ) @@ -72,7 +73,8 @@ def find(self, company_id, user_id): else: key = "name" - for trade in self.get(company_id=company_id): + all_trades = self.get(company_id=company_id) + for trade in all_trades: if trade[key] == user_id: return trade diff --git a/ProPyCore/procore.py b/ProPyCore/procore.py index 92383f5..2e50ea0 100644 --- a/ProPyCore/procore.py +++ b/ProPyCore/procore.py @@ -40,6 +40,9 @@ def __init__(self, client_id, client_secret, redirect_uri, base_url, oauth_url) self.reset_access_token() # create instances of procore endpoints + self._init_endpoints() + + def _init_endpoints(self): # General self.companies = companies.Companies(access_token=self.__access_token, server_url=self.__base_url) self.projects = projects.Projects(access_token=self.__access_token, server_url=self.__base_url) @@ -93,6 +96,7 @@ def reset_access_token(self): Gets a new access token """ self.__access_token = self.get_access_token() + self._init_endpoints() def print_attributes(self): """ diff --git a/setup.py b/setup.py index f23844a..a99a3d1 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setup( name="ProPyCore", - version="0.6.3", + version="0.6.4", author="Hagen E. Fritz", author_email="hfritz@r-o.com", description="Interact with Procore through Python for data connection applications", From 890cc34fb72adf39300c5179bae24447af41b1d3 Mon Sep 17 00:00:00 2001 From: Hagen Fritz Date: Wed, 28 May 2025 13:28:44 -0500 Subject: [PATCH 3/3] docs: add changes for v0.6.4. --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5171771..24f33bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [0.6.4] - 2025-05-28 + +### Added +* `directory.ipynb`: add snippets for trades, users, and vendors directory endpoints in snippet + +### Changed +* `procore.py`: update refresh token process so that the submodules are reinitialized with the new access token + ## [0.6.3] - 2025-05-13 ### Added