update_companies.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. from __future__ import annotations
  2. import time
  3. import itertools
  4. import json
  5. import sys
  6. import tomllib
  7. import typing
  8. import httpx
  9. def main() -> None:
  10. (month,) = sys.argv[1:]
  11. with open('config.toml', 'rb') as f:
  12. config = tomllib.load(f)
  13. fio_api_key = config['fio_api_key']
  14. with open(f'www/data/company-data-{month}.json', 'r') as f:
  15. company_ids = frozenset(json.load(f)['totals'])
  16. print(len(company_ids), 'companies in', month, 'data')
  17. with open('www/data/knownCompanies.json', 'r') as f:
  18. companies = json.load(f)
  19. now = int(time.time())
  20. cached: set[str] = set()
  21. for co in companies.values():
  22. co_last_update = co.get('LastUpdated')
  23. if co_last_update is not None and now - co_last_update < 24 * 60 * 60:
  24. cached.add(co['Username'].casefold())
  25. print(len(cached), 'cached companies')
  26. all_users: typing.Collection[str] = httpx.get('https://rest.fnar.net/user/allusers',
  27. headers={'Authorization': fio_api_key}).raise_for_status().json()
  28. try:
  29. for username in all_users:
  30. update_company_from_fio(companies, cached, now, username)
  31. finally:
  32. with open('www/data/knownCompanies.json', 'w') as f:
  33. json.dump(companies, f)
  34. unknown_companies = company_ids - companies.keys()
  35. print(len(unknown_companies), 'unknown companies')
  36. try:
  37. for batch in itertools.batched(unknown_companies, 10):
  38. company_data: typing.Collection[FIOCompany | None] = httpx.get('https://api.fnar.net/company/lookup',
  39. params={'company': batch}).raise_for_status().json()
  40. for fio_co in company_data:
  41. if fio_co is None:
  42. continue
  43. known_user = {'Username': fio_co['UserName'], 'LastUpdated': now}
  44. if (corp := fio_co.get('CorporationCode')) and corp != 'UNKN':
  45. known_user['Corporation'] = corp
  46. print(known_user)
  47. assert fio_co['CompanyId'] not in companies
  48. companies[fio_co['CompanyId']] = known_user
  49. finally:
  50. with open('www/data/knownCompanies.json', 'w') as f:
  51. json.dump(companies, f)
  52. fio_rest_users = frozenset(user.casefold() for user in all_users)
  53. known_usernames = frozenset(co['Username'].casefold() for co in companies.values() if co['Username'] is not None)
  54. users_without_fio_accounts = known_usernames - fio_rest_users
  55. print(len(users_without_fio_accounts), 'users without FIO REST accounts')
  56. try:
  57. for username in users_without_fio_accounts:
  58. update_company_from_fio(companies, cached, now, username)
  59. finally:
  60. with open('www/data/knownCompanies.json', 'w') as f:
  61. json.dump(companies, f)
  62. def update_company_from_fio(companies: dict[str, dict[str, typing.Any]], cached: typing.AbstractSet[str], now: int,
  63. username: str) -> None:
  64. if username.casefold() in cached:
  65. return
  66. response = get_with_retry('https://rest.fnar.net/user/' + username)
  67. if response.status_code in (204, 404):
  68. print(username, 'not found')
  69. return
  70. fio_user = response.json()
  71. assert username.casefold() == fio_user['UserName'].casefold(), f'{username} != {fio_user["UserName"]}'
  72. known_user = {'Username': username, 'LastUpdated': now}
  73. if corp := fio_user.get('CorporationCode'):
  74. known_user['Corporation'] = corp
  75. if known_user == companies.get(fio_user['CompanyId']):
  76. print(username, 'had no change')
  77. else:
  78. print(known_user)
  79. companies[fio_user['CompanyId']] = known_user
  80. def get_with_retry(url) -> httpx.Response:
  81. for attempt in range(10):
  82. try:
  83. response = httpx.get(url)
  84. except httpx.TransportError as e:
  85. print(e, 'retrying in', attempt)
  86. time.sleep(attempt)
  87. continue
  88. if response.status_code in (204, 404) or response.is_success:
  89. return response
  90. elif attempt == 9:
  91. response.raise_for_status()
  92. print(response.status_code, 'retrying in', attempt)
  93. time.sleep(attempt)
  94. raise AssertionError
  95. class FIOCompany(typing.TypedDict):
  96. CompanyId: str
  97. UserName: str
  98. CorporationCode: str | None
  99. if __name__ == '__main__':
  100. main()