Helpers to authenticate and programmatically use external websites.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

84 lines
2.4 KiB

2 years ago
  1. from urllib.parse import urljoin
  2. import requests
  3. from lxml.etree import HTML
  4. from .exceptions import *
  5. class WebClientForm:
  6. def __init__(self, form):
  7. self.action = form.attrib['action']
  8. self.method = form.attrib.get('method', "GET").upper()
  9. self.data = {i.attrib['name']: i.attrib.get('value', "")
  10. for i in form.xpath("//input")
  11. if 'name' in i.attrib}
  12. def update(self, items):
  13. for k, v in items.items():
  14. self[k] = v
  15. def __getitem__(self, k):
  16. return self.data[k]
  17. def __setitem__(self, k, v):
  18. self.data.update({k: v})
  19. def __contains__(self, k):
  20. return k in self.data
  21. def __iter__(self):
  22. return self.data.items()
  23. def __repr__(self):
  24. return str((self.method, self.action, self.data))
  25. class WebClient(requests.Session):
  26. def __init__(self, headers={}, **kwargs):
  27. self.url = ""
  28. self.res = None
  29. self.form = None
  30. super().__init__(**kwargs)
  31. self.headers.update(headers)
  32. def send(self, *args, **kwargs):
  33. r = super().send(*args, **kwargs)
  34. self.res = r
  35. self.url = r.url
  36. self.status_code = r.status_code
  37. self.form = None
  38. return r
  39. def find_forms(self, **filters):
  40. if not self.res:
  41. return []
  42. tree = HTML(self.res.content)
  43. filters_xpath = [f"[@{k}='{v}']" for k, v in filters.items()]
  44. filters_xpath = "".join(filters_xpath)
  45. return tree.xpath("//form" + filters_xpath)
  46. def select_form(self, **filters):
  47. forms = self.find_forms(**filters)
  48. if len(forms) != 1:
  49. if forms:
  50. raise TooManyFormsError(self.res, filters)
  51. else:
  52. raise FormNotFoundError(self.res, filters)
  53. self.form = WebClientForm(forms[0])
  54. def update_form(self, **arguments):
  55. if not self.form:
  56. raise NoFormSelectedError('update_form')
  57. def submit_form(self):
  58. if not self.form:
  59. raise NoFormSelectedError('submit_form')
  60. if self.form.method not in ["POST"]:
  61. raise NotImplementedError(
  62. f"Submit method '{self.form.method}' not supported.")
  63. self.request(
  64. self.form.method,
  65. urljoin(self.url, self.form.action),
  66. data=self.form.data
  67. )