commit f4646d430367580acabb23500a7c1fba8d9fa664 Author: Alexeev Nickolay Date: Tue Sep 4 02:20:49 2018 +0300 inital diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b36b4d2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 zt50tz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b518a4a --- /dev/null +++ b/README.md @@ -0,0 +1,60 @@ +# yandex_connect + +Библиотека python для использования API Yandex connect / Яндекс коннект. +В настоящий момент реализованы все функции Directory, версии 6. + +https://tech.yandex.ru/connect/directory/api/about-docpage/ + +### Установка + +```bash +git clone https://github.com/zt50tz/yandex-connect +sudo python setup.py install +``` + +### Пример + +```python +from yandex_connect import YandexConnectDirectory +app = YandexConnectDirectory('', org_id=None) # создание +app.user_add('test', 'test234test') # добавление сотрудника +app.user_list_full() # просмотр всех сотрудников +``` + +Методы +------ + +##### Сотрудники +- ```user_info``` — Получение информации о сотруднике +- ```user_list``` - Получение списка сотрудников +- ```user_list_full``` - Получение полного списка сотрудников, без страниц +- ```user_add``` - Добавление сотрудника +- ```user_upd``` - Изменение сотрудника +- ```user_alias_add``` - Добавление алиаса для сотрудника + +##### Отделы +- ```department_list``` - Получение списка отделов +- ```department_list_full``` - Получение полного списка отделов +- ```department_info``` - Получение информации об отделе +- ```department_add``` - Добавление отдела +- ```department_upd``` - Изменение отдела +- ```department_del``` - Удаление отдела + +##### Команды +- ```group_list``` - Список команд +- ```group_list_full``` - Полный список команд +- ```group_info``` - Получение информации о команде +- ```group_add``` - Добавление команды +- ```group_upd``` - Изменение команды +- ```group_member_list``` - Участники команды +- ```group_member_add``` - Добавить участника команды +- ```group_member_del``` - Удалить участника команды +- ```group_member_update``` - Изменение участников команды + +##### Домены +- ```domain_list``` - Получение списка доменов +- ```domain_add``` - Добавить домен +- ```domain_del``` - Удалить домен + +##### Организации +- ```organization_list``` - Список организаций diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..cf9a35c --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +# coding: utf8 + +from setuptools import setup + +setup(name='yandex_connect', + version='0.01b', + description='API Yandex Connect', + url='http://github.com/zt50tz/yandex-connect', + author='Alexeev Nick', + author_email='n@akolka.ru', + license='MIT', + packages=['yandex_connect'], + install_requires=[ + 'requests', + ], + zip_safe=False) diff --git a/yandex_connect/__init__.py b/yandex_connect/__init__.py new file mode 100644 index 0000000..ca8d885 --- /dev/null +++ b/yandex_connect/__init__.py @@ -0,0 +1,4 @@ +# coding: utf8 + +from .base import * +from .directory import * \ No newline at end of file diff --git a/yandex_connect/base.py b/yandex_connect/base.py new file mode 100644 index 0000000..ebc1726 --- /dev/null +++ b/yandex_connect/base.py @@ -0,0 +1,203 @@ +# coding: utf8 + +""" +Yandex.Connect Base API module +:author: Alexeev Nick +:email: n@akolka.ru +:version: 0.01b +""" + +import json +import requests +import datetime +import inspect + + +def json_prepare_dump(obj): + """ + Подготовка к json.dumps + :param obj: объект + :return: подготовленный объект + """ + if isinstance(obj, list): + for i, item in enumerate(obj): + obj[i] = json_prepare_dump(item) + elif isinstance(obj, dict): + for key in obj: + obj[key] = json_prepare_dump(obj[key]) + elif type(obj) is datetime.date: + return obj.isoformat() + elif isinstance(obj, datetime.datetime): + return obj.isoformat() + return obj + + +def inspect_args_func(frame): + """ + Inspect current def arguments + :param frame: inspect.currentframe() + :return: dict + """ + args, _, _, values = inspect.getargvalues(frame) + return {key: values[key] for key in args if key != 'self'} + + +class YandexConnectException(Exception): + """ + Exception of module + """ + pass + + +class YandexConnectExceptionY(Exception): + """ + Exception by yandex request + """ + pass + + +class YandexConnectRequest(object): + """ Yandex Connect request API object """ + + _version = None # API version + _oauth_token = None # OAuth Token + _org_id = None # Org ID + _domain = None # Domain + + def __init__(self, domain, oauth_token, org_id=None, version=6): + """ + Init + :param domain: yandex domain + :param oauth_token: OAuth Token — https://oauth.yandex.ru/ + :param org_id: Organization id + :param version: API version + """ + self._domain = domain + self._oauth_token = oauth_token + self._org_id = org_id + if version: + self._version = version + + def __call__(self, name, data=None, method='post'): + """ + Base request method + :param name: url path + :param data: data / args of request + :param method: request method - get/post + :raise YandexConnectException: bad request, jsonify failed + :raise YandexConnectExceptionY: yandex exception + :return: dict + """ + url = '%(domain)s/v%(version)s/%(name)s' % { + 'domain': self._domain, + 'version': self._version, + 'name': name + } + if not url.endswith('/'): + url = '%s/' % url + + if data: + for key in data.keys(): + if data[key] is None or (isinstance(data[key], dict) and not data[key]): + del data[key] + continue + method = method.lower() + if method not in ['get', 'post', 'patch', 'delete']: + raise ValueError('Not right method') + kwargs = { + 'headers': { + 'Authorization': 'OAuth %s' % self._oauth_token, + 'X-Org-ID': self._org_id if self._org_id else None + } + } + if method in ['post', 'patch']: + if data: + module = name.split('/')[0] + if module.endswith('s'): + module = module[:-1] + key_id = '%s_id' % module + if key_id in data: + del data[key_id] + kwargs['data'] = json.dumps(json_prepare_dump(data)) + kwargs['headers']['Content-Type'] = 'application/json' + else: + kwargs['params'] = data + + if not kwargs['headers']['X-Org-ID']: + del kwargs['headers']['X-Org-ID'] + + try: + r = getattr(requests, method)(url, **kwargs) + except Exception: + raise YandexConnectException(u'Request error: send', name, data) + if r.status_code > 299: + try: + msg = r.json() + except Exception: + msg = r.text + raise YandexConnectExceptionY(r.status_code, msg) + if method == 'delete': + return True + try: + ret = r.json() + except Exception: + return True + return ret + + +class YandexConnectBase(object): + """ Yandex connect API base class""" + + DOMAIN = None # Request Domain + + request = None # Request object + + def __init__(self, oauth_token, org_id=None, version=6): + """ + :param oauth_token: OAuth token + :param org_id: ID org + :param version: API version + """ + self.request = YandexConnectRequest(self.DOMAIN, oauth_token, org_id=org_id, version=version) + + @staticmethod + def prepare_fields(fields, title_field, only_title_field=False): + """ + Prepare fields data key + :param fields: obj + :param title_field: second field + :param only_title_field: return only title field + :return: + """ + if not fields: + if not only_title_field: + fields = ['id', title_field] + else: + fields = [title_field] + if isinstance(fields, list): + fields = u','.join(fields) + return fields + + def list_full(self, callback, default_field, **kwargs): + """ + List full + :param callback: callback function + :param default_field: default field + :param kwargs: params + :return: list + """ + kwargs['fields'] = self.prepare_fields(kwargs['fields'], default_field) + kwargs['per_page'] = 100 + pages = None + page = 1 + ret = [] + while True: + kwargs['page'] = page + r = callback(**kwargs) + if pages is None: + pages = r['pages'] + ret += r['result'] + if page >= pages: + break + page += 1 + return ret diff --git a/yandex_connect/directory.py b/yandex_connect/directory.py new file mode 100644 index 0000000..8f5175a --- /dev/null +++ b/yandex_connect/directory.py @@ -0,0 +1,398 @@ +# coding: utf8 + +""" +Yandex.Connect Directory API module +:author: Alexeev Nick +:email: n@akolka.ru +:version: 0.01b +""" + +from .base import * +from inspect import currentframe + + +class YandexConnectDirectory(YandexConnectBase): + """ Yandex connect directory API base class """ + + DOMAIN = u'https://api.directory.yandex.net' # Request Domain + + # ------------------------------------------------------------------------------------------------------------------ + # Helper functions + # ------------------------------------------------------------------------------------------------------------------ + + @staticmethod + def prepare_contacts(contacts): + """ + Convert contacts list of tuple to yandex data structure + :param contacts: list[('type', 'value')] + :return: list + """ + if not contacts: + return None + ret = [] + if contacts: + for i in range(len(contacts)): + if type(contacts[i]) is tuple: + item = { + "type": contacts[i][0], + "value": contacts[i][1] + } + else: + item = contacts[i] + ret.append(item) + return ret + + @staticmethod + def prepare_name(data): + """ + Prepare kwargs name words to yandex data structure + :param data: kwargs of request + :return: None + """ + data['name'] = { + 'first': data['name'], + 'last': data['secname'], + 'middle': data['sername'] + } + for key in data['name'].keys(): + if not data['name'][key]: + del data['name'][key] + for key in ['secname', 'sername']: + del data[key] + + # ------------------------------------------------------------------------------------------------------------------ + # User + # ------------------------------------------------------------------------------------------------------------------ + + def user_info(self, user_id, fields=None): + """ + Получение информации о сотруднике + :param user_id: ID + :param fields: поля, по умолчанию: id, nickname + :url man: https://tech.yandex.ru/connect/directory/api/concepts/users/read-user-docpage/ + :return: yandex request dict — информация о сотруднике + """ + data = inspect_args_func(currentframe()) + data['fields'] = self.prepare_fields(data['fields'], 'nickname') + return self.request('users/%s' % user_id, data, method='get') + + def user_list(self, fields=None, id=None, nickname=None, department_id=None, recursive_department_id=None, group_id=None, recursive_group_id=None, is_dismissed=None, page=None, per_page=None): + """ + Получение списка сотрудников + :param fields: поля, по умолчанию id, nickname + :param id: фильтр + :param nickname: фильтр + :param department_id: фильтр + :param recursive_department_id: фильтр + :param group_id: фильтр + :param recursive_group_id: фильтр + :param is_dismissed: фильтр + :param page: страница + :param per_page: на странице + :url man: https://tech.yandex.ru/connect/directory/api/concepts/users/read-users-list-docpage/ + :return: yandex request dict — список сотрудников + """ + data = inspect_args_func(currentframe()) + data['fields'] = self.prepare_fields(data['fields'], 'nickname') + return self.request('users', data, method='get') + + def user_list_full(self, fields=None, id=None, nickname=None, department_id=None, recursive_department_id=None, group_id=None, recursive_group_id=None, is_dismissed=None): + """ + Получение полного списка сотрудников, без страниц + :param fields: поля, по умолчанию id, nickname + :param id: фильтр + :param nickname: фильтр + :param department_id: фильтр + :param recursive_department_id: фильтр + :param group_id: фильтр + :param recursive_group_id: фильтр + :param is_dismissed: фильтр + :url man: https://tech.yandex.ru/connect/directory/api/concepts/users/read-users-list-docpage/ + :return: yandex request list - список сотрудников + """ + return self.list_full(self.user_list, 'nickname', **inspect_args_func(currentframe())) + + def user_add(self, nickname, password, about=None, aliases=None, birthday=None, contacts=None, department_id=1, gender='male', is_admin=None, is_dismissed=None, name=None, secname=None, sername=None, position=None): + """ + Добавление сотрудника + :param nickname: логин + :param password: пароль + :param about: описание + :param aliases: list, ['псевдоним1', ...] + :param birthday: datetime.date, день рождения + :param contacts: list, Контакты в типах яндекса, либо [tuple('type', 'value'), ...] + :param department_id: ID отдела, 1 + :param gender: Пол — male|female + :param is_admin: bool, Администор + :param is_dismissed: bool, Увольнение + :param name: Имя + :param secname: Фамилия + :param sername: Отчество + :param position: Должность + :url man: https://tech.yandex.ru/connect/directory/api/concepts/users/add-user-docpage/ + :return: yandex request dict — созданный сотрудник + """ + data = inspect_args_func(currentframe()) + self.prepare_name(data) + data['contacts'] = self.prepare_contacts(data['contacts']) + return self.request('users', data, method='post') + + def user_upd(self, user_id, nickname=None, password=None, about=None, birthday=None, contacts=None, department_id=None, gender=None, is_admin=None, is_dismissed=None, name=None, secname=None, sername=None, position=None): + """ + Изменение сотрудника + :param user_id: ID сотрудника + :param nickname: логин + :param password: пароль + :param about: описание + :param aliases: list, ['псевдоним1', ...] + :param birthday: datetime.date, день рождения + :param contacts: list, Контакты в типах яндекса, либо [tuple('type', 'value'), ...] + :param department_id: ID отдела, 1 + :param gender: Пол — male|female + :param is_admin: bool, Администор + :param is_dismissed: bool, Увольнение + :param name: Имя + :param secname: Фамилия + :param sername: Отчество + :param position: Должность + :url man: https://tech.yandex.ru/connect/directory/api/concepts/users/edit-user-docpage/ + :return: yandex request dict — измененный сотрудник + """ + data = inspect_args_func(currentframe()) + self.prepare_name(data) + data['contacts'] = self.prepare_contacts(data['contacts']) + return self.request('users/%s' % user_id, data, method='patch') + + def user_alias_add(self, user_id, name): + """ + Добавление алиаса для сотрудника + :param user_id: ID сотрудника + :param name: алиас + :url man: https://tech.yandex.ru/connect/directory/api/concepts/users/add-user-aliases-docpage/ + :return: yandex request dict + """ + return self.request('users/%s/aliases' % user_id, inspect_args_func(currentframe()), method='post') + + # ------------------------------------------------------------------------------------------------------------------ + # Department + # ------------------------------------------------------------------------------------------------------------------ + + def department_list(self, fields=None, page=None, per_page=None): + """ + Получение списка отделов + :param fields: поля, по умолчанию - id, name + :param page: страница + :param per_page: количество элементов на странице + :url man: https://tech.yandex.ru/connect/directory/api/concepts/departments/read-departments-list-docpage/ + :return: yandex request list - список отделов + """ + data = inspect_args_func(currentframe()) + data['fields'] = self.prepare_fields(data['fields'], 'name') + return self.request('departments', data, method='get') + + def department_list_full(self, fields=None): + """ + Получение полного списка отделов + :param fields: поля, по умолчанию - id, name + :url man: https://tech.yandex.ru/connect/directory/api/concepts/departments/read-departments-list-docpage/ + :return: yandex request list - список отделов + """ + return self.list_full(self.department_list, 'name', **inspect_args_func(currentframe())) + + def department_info(self, department_id): + """ + Получение информации об отделе + :param department_id: ID + :return: yandex request dict + """ + return self.request('departments/%s' % department_id, method='get') + + def department_add(self, name, label, description=None, head_id=None, parent_id=1): + """ + Добавление отдела + :param name: название + :param label: рассылка + :param description: описание + :param head_id: id руководителя отдела + :param parent_id: id родительского отдела, 1 + :url man: https://tech.yandex.ru/connect/directory/api/concepts/departments/create-department-docpage/ + :return: yandex request dict - созданный отдел + """ + return self.request('departments', inspect_args_func(currentframe()), method='post') + + def department_upd(self, department_id, name=None, description=None, head_id=None, label=None, parent_id=None): + """ + Изменение отдела + :param department_id: ID + :param name: название + :param label: рассылка + :param description: описание + :param head_id: id руководителя отдела + :param parent_id: id родительского отдела + :url man: https://tech.yandex.ru/connect/directory/api/concepts/departments/edit-department-docpage/ + :return: yandex request dict - созданный отдел + """ + return self.request('departments/%s' % department_id, inspect_args_func(currentframe()), method='patch') + + def department_del(self, department_id): + """ + Удаление отдела + :param department_id: ID + :url man: https://tech.yandex.ru/connect/directory/api/concepts/departments/delete-department-docpage/ + :return: bool + """ + return self.request('departments/%s' % department_id, method='delete') + + # ------------------------------------------------------------------------------------------------------------------ + # Group + # ------------------------------------------------------------------------------------------------------------------ + + def group_list(self, fields=None, page=None, per_page=None): + """ + Список команд + :param fields: поля, по умолчанию — id, name + :param page: страница + :param per_page: на странице + :url man: https://tech.yandex.ru/connect/directory/api/concepts/groups/read-groups-list-docpage/ + :return: yandex request list - список команд + """ + data = inspect_args_func(currentframe()) + data['fields'] = self.prepare_fields(data['fields'], 'name') + return self.request('groups', data, method='get') + + def group_list_full(self, fields=None): + """ + Полный список команд + :param fields: поля, по умолчанию — id, name + :url man: https://tech.yandex.ru/connect/directory/api/concepts/groups/read-groups-list-docpage/ + :return: yandex request list - список команд + """ + return self.list_full(self.group_list, 'name', **inspect_args_func(currentframe())) + + def group_info(self, group_id, fields=None): + """ + Получение информации о команде + :param group_id: ID + :param fields: поля, по умолчанию — id, name + :url man: https://tech.yandex.ru/connect/directory/api/concepts/groups/read-group-docpage/ + :return: yandex request dict - команда + """ + data = inspect_args_func(currentframe()) + data['fields'] = self.prepare_fields(data['fields'], 'name') + return self.request('groups/%s' % group_id, data, method='get') + + def group_add(self, name, label, admins=None, description=None, members=None, type=None): + """ + Добавление команды + :param name: название команды + :param label: название рассылки + :param admins: [{"id": <идентификатор администратора>, "type": "user"},...] + :param description: описание + :param members: list — [{"type": "", "id": <идентификатор>},...] + :param type: тип - generic|organization_admin|robots|department_head + :url man: https://tech.yandex.ru/connect/directory/api/concepts/groups/create-group-docpage/ + :return: yandex request dict - созданная команда + """ + return self.request('groups', inspect_args_func(currentframe()), method='post') + + def group_upd(self, group_id, name=None, label=None, admins=None, description=None, members=None, type=None): + """ + Изменение команды + :param group_id: ID + :param name: название команды + :param label: название рассылки + :param admins: [{"id": <идентификатор администратора>, "type": "user"},...] + :param description: описание + :param members: list — [{"type": "", "id": <идентификатор>},...] + :param type: тип - generic|organization_admin|robots|department_head + :url man: https://tech.yandex.ru/connect/directory/api/concepts/groups/edit-group-docpage/ + :return: yandex request dict - измененная команда + """ + return self.request('groups/%s' % group_id, inspect_args_func(currentframe()), method='patch') + + def group_member_list(self, group_id): + """ + Участники команды + :param group_id: ID + :url man: https://tech.yandex.ru/connect/directory/api/concepts/groups/read-group-members-list-docpage/ + :return: yandex request list - участники команды + """ + return self.request('groups/%s/members', method='get') + + def group_member_add(self, group_id, type, id): + """ + Добавить участника команды + :param group_id: ID + :param type: Тип - user|group|department + :param id: User ID + :url man: https://tech.yandex.ru/connect/directory/api/concepts/groups/add-group-member-docpage/ + :return: yandex request dict + """ + return self.request('groups/%s/members' % group_id, inspect_args_func(currentframe()), method='post') + + def group_member_del(self, group_id, user_id): + """ + Удалить участника команды + :param group_id: ID + :param user_id: User ID + :url man: https://tech.yandex.ru/connect/directory/api/concepts/groups/bulk-add-group-member-docpage/ + :return: True + """ + return self.group_member_update(group_id, [{'operation_type': 'remove', 'value': {'id': user_id}}]) + + def group_member_update(self, group_id, actions): + """ + Изменение участников команды + :param group_id: ID + :param actions: list, [{"operation_type": "", "value": {"type": "", "id": ID User}},..] + :url man: https://tech.yandex.ru/connect/directory/api/concepts/groups/bulk-add-group-member-docpage/ + :return: True + """ + return self.request('groups/%s/members/bulk-update' % group_id, data=actions, method='post') + + # ------------------------------------------------------------------------------------------------------------------ + # Domain + # ------------------------------------------------------------------------------------------------------------------ + + def domain_list(self, fields=None): + """ + Получение списка доменов + :param fields: поля, по умолчанию name + :url man: https://tech.yandex.ru/connect/directory/api/concepts/domains/read-domains-list-docpage/ + :return: yandex request list + """ + data = inspect_args_func(currentframe()) + data['fields'] = self.prepare_fields(data['fields'], 'name', only_title_field=True) + return self.request('domains', data, method='get') + + def domain_add(self, name): + """ + Добавить домен + :param name: домен + :url man: https://tech.yandex.ru/connect/directory/api/concepts/domains/add-domain-docpage/ + :return: bool + """ + return self.request('domains', inspect_args_func(currentframe()), method='post') + + def domain_del(self, name): + """ + Удалить домен + :param name: домен + :url man: https://tech.yandex.ru/connect/directory/api/concepts/domains/delete-domain-docpage/ + :return: bool + """ + return self.request('domains/%s' % name, method='delete') + + # ------------------------------------------------------------------------------------------------------------------ + # Organization + # ------------------------------------------------------------------------------------------------------------------ + + def organization_list(self, fields=None): + """ + Список организаций + :param fields: поля, по умолчанию — id, name + :return: yandex request list + """ + data = inspect_args_func(currentframe()) + data['fields'] = self.prepare_fields(data['fields'], 'name') + return self.request('organizations', data, method='get')['result']