Azure DevOps API: ошибка «Ожидаемая ценность» и пользователи не найдены, несмотря на совпадение электронных писемPython

Программы на Python
Ответить
Anonymous
 Azure DevOps API: ошибка «Ожидаемая ценность» и пользователи не найдены, несмотря на совпадение электронных писем

Сообщение Anonymous »

Я работаю над сценарием Python для удаления прямых назначений пользователей в Azure DevOps на основе их адресов электронной почты из CSV-файла. Сценарий использует API Azure DevOps Graph для извлечения пользователей и сопоставления их mailAddress или PrincipalName с электронными письмами, указанными в CSV.
Проблема:
Когда я пытаюсь получить пользователей с помощью Конечная точка API https://vssps.dev.azure.com/{organizati ... -preview.1, я столкнулся с этой ошибкой:
ERROR:__main__:Error fetching users: Expecting value: line 3 column 1 (char 4)
The raw response indicates an empty or malformed response from the API.

Несмотря на то, что почтовый адрес совпадает (с нормализованным сравнением без учета регистра), я всегда вижу:
WARNING:__main__:User not found in Azure DevOps: john.doe@example.com. Check casing and presence in Azure DevOps.

Примечание. Адрес электронной почты john.doe@example.com существует в Azure DevOps как John.Doe@example.com.
Что я сделал Пробовал:
Нормализованные электронные письма: CSV-файл и полученный адрес электронной почты были преобразованы в нижний регистр для сравнения.
Откат к PrincipalName: использовалось PrincipalName в качестве вторичного идентификатора, если mailAddress был указан. не найден.
Журналирование: регистрируются все выбранные пользователи и их атрибуты для отладки:
logger.debug(f"Fetched users: {[user.get('mailAddress') for user in users]}")
The log shows no users being fetched (Fetched 0 users from Azure DevOps).

Проверенные разрешения API:
Токен личного доступа (PAT) имеет области «График» (чтение) и «Управление правами пользователей».
PAT работает для других вызовов API в Azure DevOps.
Особенности скрипта:
Вот функция для получения пользователей:
def fetch_all_users():
users = []
continuation_token = None
while True:
url = f"{BASE_URL}/graph/users?api-version=7.1-preview.1"
if continuation_token:
url += f"&continuationToken={continuation_token}"
try:
response = requests.get(url, headers=HEADERS, timeout=30)
logger.debug(f"Raw response: {response.status_code}, Content: {response.text}")
response.raise_for_status()
data = response.json()
users.extend(data.get("value", []))
continuation_token = data.get("continuationToken")
if not continuation_token:
break
except Exception as e:
logger.error(f"Error fetching users: {e}")
break
logger.info(f"Fetched {len(users)} users from Azure DevOps.")
return users

Среда:
URL-адрес организации: https://dev.azure.com/myorganization
URL-адрес базового API: https://vssps.dev.azure.com /myorganization/_apis
Версия API: 7.1-preview.1
Версия Python: 3.10
Вопросы:
Почему /graph/users конечная точка возвращает пустой или неверный ответ? Есть ли другая конечная точка, которую мне следует использовать?
Есть ли способ надежно получать пользователей с сопоставлением адресов электронной почты без учета регистра в Azure DevOps?
Может ли это быть связано с настройками уровня организации в Azure DevOps, которые ограничивают доступ к пользовательским данным?
Как я могу отладить это дальше, чтобы гарантировать, что пользователи правильно выбираются и сопоставляются?
Любые рекомендации или предложения будут с благодарностью приняты! Дайте мне знать, если потребуются дополнительные подробности.
Вот весь сценарий:
import os
import csv
import requests
import logging
import time
from requests.exceptions import RequestException
from collections import defaultdict
from concurrent.futures import ThreadPoolExecutor

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

ORGANIZATION = "company"
PAT = "your_actual_pat_value"

CSV_FILE_PATH = "...Report.csv"
BASE_URL = "https://vssps.dev.azure.com/company/_apis"
HEADERS = {
"Content-Type": "application/json",
"Authorization": f"Basic {requests.auth._basic_auth_str('', PAT)}"
}

def validate_csv_columns(required_columns, csv_reader):
missing_columns = [col for col in required_columns if col not in csv_reader.fieldnames]
if missing_columns:
raise ValueError(f"CSV is missing required columns: {missing_columns}. Found: {csv_reader.fieldnames}")

def fetch_all_users():
users = []
continuation_token = None
while True:
url = f"{BASE_URL}/graph/users?api-version=7.1-preview.1"
if continuation_token:
url += f"&continuationToken={continuation_token}"
try:
response = requests.get(url, headers=HEADERS, timeout=30)
logger.debug(f"Raw response: {response.status_code}, Content: {response.text}")
response.raise_for_status()
data = response.json()
users.extend(data.get("value", []))
continuation_token = data.get("continuationToken")
if not continuation_token:
break
except RequestException as e:
logger.error(f"Error fetching users: {e}")
break
except ValueError as ve:
logger.error(f"Malformed JSON response: {ve}")
break
logger.info(f"Fetched {len(users)} users from Azure DevOps.")
logger.debug(f"Fetched users: {[user.get('mailAddress') for user in users]}")
return users

def get_user_descriptor_map():
users = fetch_all_users()
user_map = defaultdict(str)
for user in users:
email = user.get("mailAddress", "").strip()
principal_name = user.get("principalName", "").strip()
descriptor = user.get("descriptor")
if email:
user_map[email.lower()] = descriptor
if principal_name and principal_name.lower() not in user_map:
user_map[principal_name.lower()] = descriptor
return user_map

def remove_direct_assignment(user_descriptor):
url = f"{BASE_URL}/graph/memberships/{user_descriptor}?api-version=7.1-preview.1"
try:
response = requests.delete(url, headers=HEADERS)
response.raise_for_status()
if response.status_code == 204:
logger.info(f"Successfully removed direct assignment for user: {user_descriptor}")
except RequestException as e:
logger.error(f"Error removing direct assignment for {user_descriptor}: {e}")

def process_users_in_parallel(user_descriptors):
with ThreadPoolExecutor(max_workers=10) as executor:
executor.map(remove_direct_assignment, user_descriptors)

def process_csv():
try:
user_map = get_user_descriptor_map()
with open(CSV_FILE_PATH, mode='r') as file:
csv_reader = csv.DictReader(file)
validate_csv_columns(["UserEmail"], csv_reader)
user_descriptors = []
for row in csv_reader:
email = row.get("UserEmail", "").strip().lower()
if not email:
logger.warning("Skipping row with missing UserEmail field")
continue
logger.info(f"Processing user: {email}")
user_descriptor = user_map.get(email)
if user_descriptor:
user_descriptors.append(user_descriptor)
else:
logger.warning(f"User not found in Azure DevOps: {email}. Check casing and presence in Azure DevOps.")
process_users_in_parallel(user_descriptors)
except FileNotFoundError:
logger.error(f"CSV file not found: {CSV_FILE_PATH}")
except ValueError as ve:
logger.error(f"CSV validation error: {ve}")
except Exception as e:
logger.error(f"Unexpected error: {e}")

if __name__ == "__main__":
try:
logger.info("Starting the process to remove direct assignments.")
process_csv()
logger.info("Script completed successfully.")
import sys
sys.exit(0)
except Exception as e:
logger.error(f"Script terminated with errors: {e}")
sys.exit(1)


Подробнее здесь: https://stackoverflow.com/questions/793 ... matching-e
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»