Skip to content

Instantly share code, notes, and snippets.

@Gabryxx7
Last active Feb 4, 2022
Embed
What would you like to do?
Automatic refresh and booking editing for Bupa Visa Medical Services

Instructions

Set up Selenium's Google Chrome driver

  1. Check your Google Chrome version (Three dots at the top right -> Settings -> From the sidebar on the left go to "About Chrome" -> Annotate your Google Chrome version)
  2. Go to https://chromedriver.chromium.org/downloads
  3. Download the right file for your OS and your Google Chrome Version
  4. Extract it and place it anywhere you'd like, annotate the path to the extracted file

Set up your python environment

I tested this on a fresh environment with Anaconda3 and Python 3.10.2 but it should work without Anaconda too!

Using pip:

pip install -r requirements.txt

Using conda

conda env create --file environment.yml -n test_web conda activate test_web

Install Pushbullet

pip install pushbullet.py==0.9.1
pip install pywebio 

Edit config.yml

  1. Fill in your details (HAP ID, name, surname, date of birth)
  2. Edit the chrome_driver_path the file path you just extracted (including the extracted filename e.g "chromedriver.exe")
  3. (Optional) Edit your date preferences:
    • Add a date range for your appointment
    • Add a list of excluded dates
    • Add a list of excluded weekdays

Set up PushBullet notifications

  1. Go to Pushbullet.com
  2. Create an account
  3. Install the Pushbullet app on your phone and login
  4. Go to your Pushbullet's account's settings (https://www.pushbullet.com/#settings)
  5. Create an access Token and copy it
  6. Add it to config.yml
  7. You'll now receive notifications on your devices through Pushbullet!

Run it!

Once all the dependencies are installed (and the conda environment is activated) just run it with python .\bupaRefresh.py

Let it run for a while, last time it took me about 2 hours to move the appointment from 3 months away to the day after!

import os
import selenium
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.expected_conditions import presence_of_element_located, element_to_be_clickable
from selenium.common.exceptions import ElementClickInterceptedException
import time
from PIL import Image
import io
import requests
import chromedriver_binary
import datetime
import oyaml as yaml
import calendar
from pushbullet import PushBullet
from pywebio.input import *
from pywebio.output import *
from pywebio.session import *
class bcolors:
HEADER = '\033[95m'
OKBLUE = '\033[94m'
OKCYAN = '\033[96m'
OKGREEN = '\033[92m'
WARNING = '\033[93m'
FAIL = '\033[91m'
ENDC = '\033[0m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
# Remember to install the chromedriver for your OS, download the version that matches your installed Google Chrome version
# https://chromedriver.chromium.org/downloads
# Global Variables
driver = None
wait = WebDriverWait(driver, 20)
data = None
pb = None
log_file = None
"""
Just setting up Selenium with the driver
"""
def read_config():
global data
global pb
data = yaml.full_load(open("config.yml"))
if "pushbullet_token" in data:
pb = PushBullet(data["pushbullet_token"])
def init_chrome_driver():
global driver
global wait
global options
options = Options()
options.headless = False # When True it will show a chrome window so you can see what's happening
options.add_argument("--window-size=400, 300")
# Initialize the Chrome Driver with the options we set up
driver = webdriver.Chrome(data["chrome_driver_path"], options=options)
# This defines a default wait object, the waiting time is 10 seconds
# Whenever you call a wait, it will wait until the even you specify happens
# If it does not happen in the time frame you passed, it will throw a TimeOutException
wait = WebDriverWait(driver, data["wait_element_time"])
def log_print(log_type="i", txt="", with_time=True, end="\n", start="\n", pb_notif=True, to_file=True):
global log_file
global pb
prefix = ""
if with_time:
prefix = f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\t"
color = ""
send_to_pb = False
log_type = log_type.lower()
if log_type in ["i","info","nfo","log"]:
color = bcolors.OKCYAN
elif log_type in ["w","f"]:
color = bcolors.WARNING
# send_to_pb = True
elif log_type in ["e","f", "n", "error", "fail", "no"]:
color = bcolors.FAIL
send_to_pb = True
elif log_type in ["s","success", "ok"]:
color = bcolors.OKGREEN
send_to_pb = True
print(start+prefix+color+txt, end=end)
if to_file and log_file is not None:
try:
log_file.write(start+prefix+txt+end+"\n")
log_file.flush()
except Exception as e:
pass
if pb is not None and pb_notif and send_to_pb:
try:
pb.push_note("Bupa Refresh", f"{prefix}\n{txt}")
except Exception as e:
pass
def wait_print(seconds, waiting_msg="", end_msg="", log_type="i", pb_notif=False):
while seconds > 0:
log_print(log_type, f"{waiting_msg} {seconds}s... ", end = "\r", start="", pb_notif=pb_notif, to_file=False)
time.sleep(1)
seconds = seconds - 1
log_print(log_type, f"{waiting_msg} {seconds}s... {end_msg}", end = "\n", start="", pb_notif=pb_notif, to_file=True)
def loop_refresh():
with open('dates_log.csv', 'w') as f:
best_date = 0
cnt = 0
# Loop forever to keep finding closer and closer appointments
while True:
try:
if cnt > 0:
# This is the power of the wait: I'm waiting until the Calendar input field is clickable
calendar_date = wait.until(element_to_be_clickable((By.ID, "ContentPlaceHolder1_SelectTime1_txtAppDate")))
else:
raise Exception("First time running!")
except Exception as e:
try:
# If the calendar input field is not found and the timeout gets triggered then it means we are either in the wrong page
# Or we have been kicked/logged out so we'll restart the login process again
# This will also get triggered the first time you run the script as there is nothing on the page so after 10 seconds
# This exception will be thrown and the login starts
login()
# Gotta wait until the "modify/edit date and location" button is visible
edit_btn = wait.until(element_to_be_clickable((By.ID, "ContentPlaceHolder1_repAppointments_lnkChangeAppointment_0")))
edit_btn.click()
# Now need to wait for the next button to be clickable
next_btn = wait.until(element_to_be_clickable((By.ID, "ContentPlaceHolder1_btnCont")))
next_btn.click()
# Once we are logged in and moved to the edit booking page we should be able to find the calendar input field
calendar_date = wait.until(element_to_be_clickable((By.ID, "ContentPlaceHolder1_SelectTime1_txtAppDate")))
except Exception as e:
log_print("e", "Error while logging in! {e}", end="")
# Now I'm just getting the new available date from the calendar in the page and comparing it
# With the current appointment date
calendar_date.click()
new_date = calendar_date.get_attribute("value")
new_appt_obj = datetime.datetime.strptime(new_date, '%d/%m/%Y')
try:
f.write(f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')},{new_appt_obj}\n")
f.flush()
except Exception as e:
pass
# If we found a closer date than the current one
curr_appt_date = datetime.datetime.strptime(data["appt_date"], '%d/%m/%Y') # Always need to re-read it in case it has changed!
if check_date(new_appt_obj, curr_appt_date):
book_appointment(driver, data, new_date)
else:
# If this date does not work, there might be other dates in the calendar that work!
# Let's extract them and see
try:
calendar_month = driver.find_element(By.CLASS_NAME, "ui-datepicker-month").text
calendar_year = driver.find_element(By.CLASS_NAME, "ui-datepicker-year").text
days = driver.find_elements(By.CSS_SELECTOR, ".Highlighted:not(.ui-datepicker-current-day)")
for available_day in days:
day = available_day.text
log_print("i", f"Checking date from calendar {day}/{calendar_month}/{calendar_year}")
new_date = f"{day}/{calendar_month}/{calendar_year}"
new_appt_obj = datetime.datetime.strptime(f"{day}/{calendar_month}/{calendar_year}", '%d/%B/%Y')
if check_date(new_appt_obj, curr_appt_date):
available_day.click()
book_appointment(driver, data, new_date)
except Exception as e:
log_print("e", f"Error getting other dates from calendar! {e}", end="")
print()
wait_print(data["time_to_refresh"], waiting_msg=f"No new date found or not a good option! Waiting {data['time_to_refresh']}s before refresh...",
end_msg="Refreshing the page!", log_type="w")# take a pause
driver.refresh() # Refresh the page and restart the loop which will look for the calendar
time.sleep(2) # take a pause
cnt += 1
def book_appointment(driver, data, new_date):
time_results = wait.until(element_to_be_clickable((By.CSS_SELECTOR, '#ContentPlaceHolder1_SelectTime1_rblResults input')))
time_results_label = driver.find_elements_by_css_selector('#ContentPlaceHolder1_SelectTime1_rblResults label')
# Get the radio buttons and their labels for the available times
log_print("i", f"New date and time found! {new_date} {time_results_label[-1].text}")
# And because I'm lazy, I'll always click the last radio button in the list (so the latest time of the day available)
time_results[-1].click()
# After clicking we need to wait to be able to click next
next_btn_edit = wait.until(element_to_be_clickable((By.ID, 'ContentPlaceHolder1_btnCont')))
next_btn_edit.click()
# After clicking next we need to wait until the confirmation page shows up
# Because the Save Changes button has no class or id I used XPATH to get the button with that specific text in it
save_btn = wait.until(element_to_be_clickable((By.XPATH, '//button[text()="Save changes"]')))
log_print("i", f"Found Save button! {save_btn.get_attribute('onclick')}")
save_btn.click() # Click Save!
# Update the date in the yaml file so at the next loop it won't keep booking this same one and get stuck in a loop
data["appt_date"] = new_date
yaml.dump(data, open("config.yml", "w"))
def check_date(date_to_check, curr_appt_date):
log_print("i", f"Checking: \t{curr_appt_date} - {date_to_check}", end="\t")
if date_to_check > curr_appt_date:
log_print("w", f"New date is AFTER the current booked date", end="")
return False
dates_range=data.get("dates_range")
from_date = None
to_date = None
if dates_range is not None:
from_date= dates_range.get("from")
to_date= dates_range.get("to")
if from_date is not None and date_to_check < datetime.datetime.strptime(from_date, '%d/%m/%Y'):
log_print("w", f"New date {date_to_check} is OUTSIDE the RANGE (BEFORE the FROM date {from_date})", end="")
return False
if to_date is not None and date_to_check > datetime.datetime.strptime(to_date, '%d/%m/%Y'):
log_print("w", f"New date {date_to_check} is OUTSIDE the RANGE (AFTER the TO date {to_date})", end="")
return False
weekdays_excluded = data.get("dates_weekdays_excluded")
if weekdays_excluded is not None:
for weekday_str in weekdays_excluded:
new_weekday = calendar.day_name[date_to_check.weekday()].lower()
weekday_ex = weekday_str.lower()
# log_print(f"{new_weekday} - {weekday_str}", end="\t")
if new_weekday == weekday_ex:
log_print("w", f"New date {date_to_check} is a {weekday_str.upper()}, which is EXCLUDED", end="")
return False
dates_excluded = data.get("dates_excluded")
if dates_excluded is not None:
for date_ex_str in dates_excluded:
date_ex = datetime.datetime.strptime(date_ex_str, '%d/%m/%Y')
if date_ex == date_to_check:
log_print("w", f"New date {date_to_check} is in the EXCLUDED list", end="")
return False
log_print("ok", f"New date {date_to_check} WORKS! BOOKING!", end="")
return True
def test_dates(dates_to_check):
for date in dates_to_check:
check_date(datetime.datetime.strptime(date, '%d/%m/%Y'))
"""
Function to login by inputting the data and moving to the edit booking page
"""
def login():
driver.get(data["bupa_base_url"]) # Navigate to the BUPA modify booking page
# Get all the field and send the input for each one
# driver.maximize_window()
time.sleep(2)
search_btn = wait.until(element_to_be_clickable((By.ID, "ContentPlaceHolder1_btnSearch")))
hapid = driver.find_element(By.ID, 'ContentPlaceHolder1_txtHAPID')
hapid.send_keys(data["HAPID_data"])
firstname = driver.find_element(By.ID, 'ContentPlaceHolder1_txtFirstName')
firstname.send_keys(data["first_name_data"])
surname = driver.find_element(By.ID, 'ContentPlaceHolder1_txtSurname')
surname.send_keys(data["surname_data"])
dob = driver.find_element(By.ID, 'ContentPlaceHolder1_txtDOB')
dob.send_keys(data["dob_data"])
# Finally click search to get to our booking
search_btn.click()
if __name__ == "__main__":
log_file = open(f"log_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt", "w")
read_config()
# test_dates(["01/01/2021", "29/01/2022", "30/01/2022", "31/12/2021", "01/01/2022", "01/03/2022", "10/02/2022", "01/02/2022", "15/01/2022", "12/12/2022", "01/01/2023"])
init_chrome_driver()
try:
loop_refresh()
except Exception as e:
log_print("e", "Critical ERROR!! {e}", end="")
close(log_file)
bupa_base_url: "https://bmvs.onlineappointmentscheduling.net.au/oasis/Search.aspx"
time_to_refresh: 15
wait_element_time: 10
pushbullet_token: "xxxxxxxxxxxx"
chrome_driver_path: "C:\\chromedriver\\chromedriver.exe" # Windows
# chrome_driver_path: "/Users/gabry/Documents/chromedriver/chromedriver # MacOS
# Booking details
HAPID_data: '12345678'
first_name_data: Jane
surname_data: Doe
dob_data: 01/01/1970
appt_date: 14/03/2022
date_format: '%d/%m/%Y'
# Leave these lines commented out (with a # in front) if any day between TODAY and the APPOINTMENT DATE works for you!
dates_range:
# from: '01/01/2022'
# to: '31/12/2022'
dates_weekdays_excluded:
# - "saturday"
# - "sunday"
dates_excluded:
# - '02/01/2022'
# - '10/12/2022'
name: web
channels:
- conda-forge
- defaults
dependencies:
- ca-certificates=2021.10.8=h5b45459_0
- openssl=3.0.0=h8ffe710_2
- oyaml=1.0=pyhd8ed1ab_0
- pip=21.2.4=pyhd8ed1ab_0
- python=3.8.12=h900ac77_1_cpython
- python-chromedriver-binary=98.0.4758.48.0=py38haa244fe_0
- python_abi=3.8=2_cp38
- pyyaml=6.0=py38h294d835_3
- setuptools=58.2.0=py38haa244fe_0
- sqlite=3.36.0=h8ffe710_2
- ucrt=10.0.20348.0=h57928b3_0
- vc=14.2=hb210afc_5
- vs2015_runtime=14.29.30037=h902a5da_5
- wheel=0.37.0=pyhd8ed1ab_1
- yaml=0.2.5=h8ffe710_2
- pip:
- amazoncaptcha==0.5.2
- certifi==2021.5.30
- chardet==4.0.0
- colorama==0.4.4
- configparser==5.0.2
- crayons==0.4.0
- distlib==0.3.4
- filelock==3.4.2
- idna==2.10
- logger==1.4
- pillow==8.3.2
- platformdirs==2.4.1
- python-dotenv==0.15.0
- requests==2.25.1
- selenium==3.141.0
- six==1.16.0
- urllib3==1.26.7
- virtualenv==20.13.0
- webdriver-manager==3.4.2
prefix: D:\Programs\Anaconda3\envs\web
alabaster @ file:///home/ktietz/src/ci/alabaster_1611921544520/work
anaconda-client==1.7.2
anaconda-navigator==2.0.3
anaconda-project @ file:///tmp/build/80754af9/anaconda-project_1610472525955/work
anyio @ file:///C:/ci/anyio_1620153418380/work/dist
appdirs==1.4.4
argh==0.26.2
argon2-cffi @ file:///C:/ci/argon2-cffi_1613037959010/work
asn1crypto @ file:///tmp/build/80754af9/asn1crypto_1596577642040/work
astroid @ file:///C:/ci/astroid_1613501047216/work
astropy @ file:///C:/ci/astropy_1617745647203/work
async-generator @ file:///home/ktietz/src/ci/async_generator_1611927993394/work
atomicwrites==1.4.0
attrs @ file:///tmp/build/80754af9/attrs_1604765588209/work
autopep8 @ file:///tmp/build/80754af9/autopep8_1615918855173/work
Babel @ file:///tmp/build/80754af9/babel_1607110387436/work
backcall @ file:///home/ktietz/src/ci/backcall_1611930011877/work
backports.functools-lru-cache @ file:///tmp/build/80754af9/backports.functools_lru_cache_1618170165463/work
backports.shutil-get-terminal-size @ file:///tmp/build/80754af9/backports.shutil_get_terminal_size_1608222128777/work
backports.tempfile @ file:///home/linux1/recipes/ci/backports.tempfile_1610991236607/work
backports.weakref==1.0.post1
bcrypt @ file:///C:/ci/bcrypt_1597936263757/work
beautifulsoup4 @ file:///home/linux1/recipes/ci/beautifulsoup4_1610988766420/work
bitarray @ file:///C:/ci/bitarray_1618435038389/work
bkcharts==0.2
black==19.10b0
bleach @ file:///tmp/build/80754af9/bleach_1612211392645/work
bokeh @ file:///C:/ci/bokeh_1620784067744/work
boto==2.49.0
Bottleneck==1.3.2
brotlipy==0.7.0
certifi==2020.12.5
cffi @ file:///C:/ci/cffi_1613247279197/work
chardet @ file:///C:/ci/chardet_1607690654534/work
chromedriver-binary==97.0.4692.36.0
click @ file:///home/linux1/recipes/ci/click_1610990599742/work
cloudpickle @ file:///tmp/build/80754af9/cloudpickle_1598884132938/work
clyent==1.2.2
colorama @ file:///tmp/build/80754af9/colorama_1607707115595/work
comtypes==1.1.9
conda==4.11.0
conda-build==3.21.4
conda-content-trust @ file:///tmp/build/80754af9/conda-content-trust_1617045594566/work
conda-package-handling @ file:///C:/ci/conda-package-handling_1618262320430/work
conda-repo-cli @ file:///tmp/build/80754af9/conda-repo-cli_1620168426516/work
conda-token @ file:///tmp/build/80754af9/conda-token_1620076980546/work
conda-verify==3.4.2
contextlib2==0.6.0.post1
cryptography @ file:///C:/ci/cryptography_1616769344312/work
cycler==0.10.0
Cython @ file:///C:/ci/cython_1618435363327/work
cytoolz==0.11.0
dask @ file:///tmp/build/80754af9/dask-core_1617390489108/work
decorator @ file:///tmp/build/80754af9/decorator_1617916966915/work
defusedxml @ file:///tmp/build/80754af9/defusedxml_1615228127516/work
diff-match-patch @ file:///tmp/build/80754af9/diff-match-patch_1594828741838/work
distributed @ file:///C:/ci/distributed_1617384289923/work
docutils @ file:///C:/ci/docutils_1617481617511/work
entrypoints==0.3
et-xmlfile==1.0.1
fastcache==1.1.0
filelock @ file:///home/linux1/recipes/ci/filelock_1610993975404/work
flake8 @ file:///tmp/build/80754af9/flake8_1615834841867/work
Flask @ file:///home/ktietz/src/ci/flask_1611932660458/work
fsspec @ file:///tmp/build/80754af9/fsspec_1617959894824/work
future==0.18.2
gevent @ file:///C:/ci/gevent_1616773090559/work
glob2 @ file:///home/linux1/recipes/ci/glob2_1610991677669/work
greenlet @ file:///C:/ci/greenlet_1611958565931/work
h11==0.12.0
h5py==2.10.0
HeapDict==1.0.1
html5lib @ file:///tmp/build/80754af9/html5lib_1593446221756/work
idna @ file:///home/linux1/recipes/ci/idna_1610986105248/work
imagecodecs @ file:///C:/ci/imagecodecs_1617996768495/work
imageio @ file:///tmp/build/80754af9/imageio_1617700267927/work
imagesize @ file:///home/ktietz/src/ci/imagesize_1611921604382/work
importlib-metadata @ file:///C:/ci/importlib-metadata_1617877484576/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
intervaltree @ file:///tmp/build/80754af9/intervaltree_1598376443606/work
ipykernel @ file:///C:/ci/ipykernel_1596190155316/work/dist/ipykernel-5.3.4-py3-none-any.whl
ipython @ file:///C:/ci/ipython_1617121002983/work
ipython-genutils @ file:///tmp/build/80754af9/ipython_genutils_1606773439826/work
ipywidgets @ file:///tmp/build/80754af9/ipywidgets_1610481889018/work
isort @ file:///tmp/build/80754af9/isort_1616355431277/work
itsdangerous @ file:///home/ktietz/src/ci/itsdangerous_1611932585308/work
jdcal==1.4.1
jedi @ file:///C:/ci/jedi_1606914528444/work
Jinja2 @ file:///tmp/build/80754af9/jinja2_1612213139570/work
joblib @ file:///tmp/build/80754af9/joblib_1613502643832/work
json5==0.9.5
jsonschema @ file:///tmp/build/80754af9/jsonschema_1602607155483/work
jupyter==1.0.0
jupyter-client @ file:///tmp/build/80754af9/jupyter_client_1616770841739/work
jupyter-console @ file:///tmp/build/80754af9/jupyter_console_1616615302928/work
jupyter-core @ file:///C:/ci/jupyter_core_1612213356021/work
jupyter-packaging @ file:///tmp/build/80754af9/jupyter-packaging_1613502826984/work
jupyter-server @ file:///C:/ci/jupyter_server_1616084298403/work
jupyterlab @ file:///tmp/build/80754af9/jupyterlab_1619133235951/work
jupyterlab-pygments @ file:///tmp/build/80754af9/jupyterlab_pygments_1601490720602/work
jupyterlab-server @ file:///tmp/build/80754af9/jupyterlab_server_1617134334258/work
jupyterlab-widgets @ file:///tmp/build/80754af9/jupyterlab_widgets_1609884341231/work
keyring @ file:///C:/ci/keyring_1614616910860/work
kiwisolver @ file:///C:/ci/kiwisolver_1612282606037/work
lazy-object-proxy @ file:///C:/ci/lazy-object-proxy_1616529307648/work
libarchive-c @ file:///tmp/build/80754af9/python-libarchive-c_1617780486945/work
llvmlite==0.36.0
locket==0.2.1
lxml @ file:///C:/ci/lxml_1616443455957/work
MarkupSafe==1.1.1
matplotlib @ file:///C:/ci/matplotlib-suite_1613408055530/work
mccabe==0.6.1
menuinst==1.4.16
mistune==0.8.4
mkl-fft==1.3.0
mkl-random @ file:///C:/ci/mkl_random_1618854156666/work
mkl-service==2.3.0
mock @ file:///tmp/build/80754af9/mock_1607622725907/work
more-itertools @ file:///tmp/build/80754af9/more-itertools_1613676688952/work
mpmath==1.2.1
msgpack @ file:///C:/ci/msgpack-python_1612287368835/work
multipledispatch==0.6.0
mypy-extensions==0.4.3
navigator-updater==0.2.1
nbclassic @ file:///tmp/build/80754af9/nbclassic_1616085367084/work
nbclient @ file:///tmp/build/80754af9/nbclient_1614364831625/work
nbconvert @ file:///C:/ci/nbconvert_1601914925608/work
nbformat @ file:///tmp/build/80754af9/nbformat_1617383369282/work
nest-asyncio @ file:///tmp/build/80754af9/nest-asyncio_1613680548246/work
networkx @ file:///tmp/build/80754af9/networkx_1598376031484/work
nltk @ file:///tmp/build/80754af9/nltk_1618327084230/work
nose @ file:///tmp/build/80754af9/nose_1606773131901/work
notebook @ file:///C:/ci/notebook_1616443715883/work
numba @ file:///C:/ci/numba_1616774458845/work
numexpr @ file:///C:/ci/numexpr_1618856738664/work
numpy @ file:///C:/ci/numpy_and_numpy_base_1618497418457/work
numpydoc @ file:///tmp/build/80754af9/numpydoc_1605117425582/work
olefile==0.46
openpyxl @ file:///tmp/build/80754af9/openpyxl_1615411699337/work
outcome==1.1.0
oyaml==1.0
packaging @ file:///tmp/build/80754af9/packaging_1611952188834/work
pandas @ file:///C:/ci/pandas_1618365634936/work
pandocfilters @ file:///C:/ci/pandocfilters_1605102497129/work
paramiko @ file:///tmp/build/80754af9/paramiko_1598886428689/work
parso==0.7.0
partd @ file:///tmp/build/80754af9/partd_1618000087440/work
path @ file:///C:/ci/path_1614022440181/work
pathlib2 @ file:///C:/ci/pathlib2_1607025069150/work
pathspec==0.7.0
patsy==0.5.1
pep8==1.7.1
pexpect @ file:///tmp/build/80754af9/pexpect_1605563209008/work
pickleshare @ file:///tmp/build/80754af9/pickleshare_1606932040724/work
Pillow @ file:///C:/ci/pillow_1617386341487/work
pkginfo==1.7.0
pluggy @ file:///C:/ci/pluggy_1615976358795/work
ply==3.11
prometheus-client @ file:///tmp/build/80754af9/prometheus_client_1618088486455/work
prompt-toolkit @ file:///tmp/build/80754af9/prompt-toolkit_1616415428029/work
psutil @ file:///C:/ci/psutil_1612298324802/work
ptyprocess @ file:///tmp/build/80754af9/ptyprocess_1609355006118/work/dist/ptyprocess-0.7.0-py2.py3-none-any.whl
py @ file:///tmp/build/80754af9/py_1607971587848/work
pycodestyle @ file:///home/ktietz/src/ci_mi/pycodestyle_1612807597675/work
pycosat==0.6.3
pycparser @ file:///tmp/build/80754af9/pycparser_1594388511720/work
pycurl==7.43.0.6
pydocstyle @ file:///tmp/build/80754af9/pydocstyle_1616182067796/work
pyerfa @ file:///C:/ci/pyerfa_1619391071834/work
pyflakes @ file:///home/ktietz/src/ci_ipy2/pyflakes_1612551159640/work
Pygments @ file:///tmp/build/80754af9/pygments_1615143339740/work
pylint @ file:///C:/ci/pylint_1617136058775/work
pyls-black @ file:///tmp/build/80754af9/pyls-black_1607553132291/work
pyls-spyder @ file:///tmp/build/80754af9/pyls-spyder_1613849700860/work
PyNaCl @ file:///C:/ci/pynacl_1595000047588/work
pyodbc===4.0.0-unsupported
pyOpenSSL @ file:///tmp/build/80754af9/pyopenssl_1608057966937/work
pyparsing @ file:///home/linux1/recipes/ci/pyparsing_1610983426697/work
pyreadline==2.1
pyrsistent @ file:///C:/ci/pyrsistent_1600141795814/work
PySocks @ file:///C:/ci/pysocks_1605287845585/work
pytest==6.2.3
python-dateutil @ file:///home/ktietz/src/ci/python-dateutil_1611928101742/work
python-jsonrpc-server @ file:///tmp/build/80754af9/python-jsonrpc-server_1600278539111/work
python-language-server @ file:///tmp/build/80754af9/python-language-server_1607972495879/work
pytz @ file:///tmp/build/80754af9/pytz_1612215392582/work
PyWavelets @ file:///C:/ci/pywavelets_1601658407916/work
pywin32==227
pywin32-ctypes==0.2.0
pywinpty==0.5.7
PyYAML==5.4.1
pyzmq==20.0.0
QDarkStyle==2.8.1
QtAwesome @ file:///tmp/build/80754af9/qtawesome_1615991616277/work
qtconsole @ file:///tmp/build/80754af9/qtconsole_1616775094278/work
QtPy==1.9.0
regex @ file:///C:/ci/regex_1617569893741/work
requests @ file:///tmp/build/80754af9/requests_1608241421344/work
rope @ file:///tmp/build/80754af9/rope_1602264064449/work
Rtree @ file:///C:/ci/rtree_1618421009405/work
ruamel-yaml-conda @ file:///C:/ci/ruamel_yaml_1616016967756/work
scikit-image==0.18.1
scikit-learn @ file:///C:/ci/scikit-learn_1614446896245/work
scipy @ file:///C:/ci/scipy_1618856128765/work
seaborn @ file:///tmp/build/80754af9/seaborn_1608578541026/work
selenium==4.1.0
Send2Trash @ file:///tmp/build/80754af9/send2trash_1607525499227/work
simplegeneric==0.8.1
singledispatch @ file:///tmp/build/80754af9/singledispatch_1614366001199/work
sip==4.19.13
six @ file:///C:/ci/six_1605187374963/work
sniffio @ file:///C:/ci/sniffio_1614030707456/work
snowballstemmer @ file:///tmp/build/80754af9/snowballstemmer_1611258885636/work
sortedcollections @ file:///tmp/build/80754af9/sortedcollections_1611172717284/work
sortedcontainers @ file:///tmp/build/80754af9/sortedcontainers_1606865132123/work
soupsieve @ file:///tmp/build/80754af9/soupsieve_1616183228191/work
Sphinx @ file:///tmp/build/80754af9/sphinx_1620777493457/work
sphinxcontrib-applehelp @ file:///home/ktietz/src/ci/sphinxcontrib-applehelp_1611920841464/work
sphinxcontrib-devhelp @ file:///home/ktietz/src/ci/sphinxcontrib-devhelp_1611920923094/work
sphinxcontrib-htmlhelp @ file:///home/ktietz/src/ci/sphinxcontrib-htmlhelp_1611920974801/work
sphinxcontrib-jsmath @ file:///home/ktietz/src/ci/sphinxcontrib-jsmath_1611920942228/work
sphinxcontrib-qthelp @ file:///home/ktietz/src/ci/sphinxcontrib-qthelp_1611921055322/work
sphinxcontrib-serializinghtml @ file:///home/ktietz/src/ci/sphinxcontrib-serializinghtml_1611920755253/work
sphinxcontrib-websupport @ file:///tmp/build/80754af9/sphinxcontrib-websupport_1597081412696/work
spyder @ file:///C:/ci/spyder_1616776239898/work
spyder-kernels @ file:///C:/ci/spyder-kernels_1614030842607/work
SQLAlchemy @ file:///C:/ci/sqlalchemy_1618090063261/work
statsmodels==0.12.2
sympy @ file:///C:/ci/sympy_1618255511605/work
tables==3.6.1
tblib @ file:///tmp/build/80754af9/tblib_1597928476713/work
terminado==0.9.4
testpath @ file:///home/ktietz/src/ci/testpath_1611930608132/work
textdistance @ file:///tmp/build/80754af9/textdistance_1612461398012/work
threadpoolctl @ file:///tmp/tmp9twdgx9k/threadpoolctl-2.1.0-py3-none-any.whl
three-merge @ file:///tmp/build/80754af9/three-merge_1607553261110/work
tifffile @ file:///tmp/build/80754af9/tifffile_1619636090847/work
toml @ file:///tmp/build/80754af9/toml_1616166611790/work
toolz @ file:///home/linux1/recipes/ci/toolz_1610987900194/work
tornado @ file:///C:/ci/tornado_1606942392901/work
tqdm @ file:///tmp/build/80754af9/tqdm_1615925068909/work
traitlets @ file:///home/ktietz/src/ci/traitlets_1611929699868/work
trio==0.19.0
trio-websocket==0.9.2
typed-ast @ file:///C:/ci/typed-ast_1610466535590/work
typing-extensions @ file:///home/ktietz/src/ci_mi/typing_extensions_1612808209620/work
ujson @ file:///C:/ci/ujson_1611241570789/work
unicodecsv==0.14.1
urllib3 @ file:///tmp/build/80754af9/urllib3_1615837158687/work
watchdog @ file:///C:/ci/watchdog_1612471251191/work
wcwidth @ file:///tmp/build/80754af9/wcwidth_1593447189090/work
webencodings==0.5.1
Werkzeug @ file:///home/ktietz/src/ci/werkzeug_1611932622770/work
widgetsnbextension==3.5.1
win-inet-pton @ file:///C:/ci/win_inet_pton_1605306167264/work
win-unicode-console==0.5
wincertstore==0.2
wrapt==1.12.1
wsproto==1.0.0
xlrd @ file:///tmp/build/80754af9/xlrd_1608072521494/work
XlsxWriter @ file:///tmp/build/80754af9/xlsxwriter_1617224712951/work
xlwings==0.23.0
xlwt==1.3.0
xmltodict @ file:///Users/ktietz/demo/mc3/conda-bld/xmltodict_1629301980723/work
yapf @ file:///tmp/build/80754af9/yapf_1615749224965/work
zict==2.0.0
zipp @ file:///tmp/build/80754af9/zipp_1615904174917/work
zope.event==4.5.0
zope.interface @ file:///C:/ci/zope.interface_1616357322857/work
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment