This commit is contained in:
Jackson Vieira 2025-12-03 14:21:52 -03:00 committed by GitHub
commit 4c50674803
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3954 changed files with 1094 additions and 616601 deletions

30
.gitignore vendored
View File

@ -1,5 +1,31 @@
# Python-generated files
__pycache__/
*.cpython*.pyc
evolutionapi.egg-info/
dist/
*.pyc
*.pyo
*.pyd
# Distribution / packaging
dist/
build/
*.egg-info/
.eggs/
# Virtual environments
.venv/
venv/
env/
ENV/
# uv
.uv/
# Ruff
.ruff_cache/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~

View File

@ -1242,4 +1242,60 @@ The WebSocket Manager has robust error handling:
3. Use logging for debugging and monitoring
4. Consider implementing a heartbeat mechanism if needed
5. Keep your API token secure and don't expose it in logs
6. Keep your API token secure and don't expose it in logs
## Contributing
This project uses Python >= 3.8 and uv:
### Setup Development Environment
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
git clone https://github.com/EvolutionAPI/evolution-client-python.git
cd evolution-client-python
uv sync
```
### Code Quality
This project uses [ruff](https://docs.astral.sh/ruff/) for linting and formatting:
```bash
# Format code
uv run ruff format
# Check linting
uv run ruff check
# Fix auto-fixable issues
uv run ruff check --fix
```
### Building and Publishing
```bash
# Build the package
uv build
# Publish to PyPI (requires credentials)
uv publish
# Or use the provided script
./publish.sh
```
### Adding Dependencies
```bash
# Add a runtime dependency
uv add package-name
# Add a development dependency
uv add --dev package-name
```
## License
MIT

View File

@ -1,81 +0,0 @@
import requests
from .exceptions import EvolutionAuthenticationError, EvolutionNotFoundError, EvolutionAPIError
from .services.instance import InstanceService
from .services.instance_operations import InstanceOperationsService
from .services.message import MessageService
from .services.call import CallService
from .services.chat import ChatService
from .services.label import LabelService
from .services.profile import ProfileService
from .services.group import GroupService
class EvolutionClient:
"""
Cliente para interagir com a API Evolution.
Args:
base_url (str): A URL base do servidor da API Evolution.
api_token (str): O token de autenticação para acessar a API.
"""
def __init__(self, base_url: str, api_token: str):
self.base_url = base_url.rstrip('/')
self.api_token = api_token
self.instances = InstanceService(self)
self.instance_operations = InstanceOperationsService(self)
self.messages = MessageService(self)
self.calls = CallService(self)
self.chat = ChatService(self)
self.label = LabelService(self)
self.profile = ProfileService(self)
self.group = GroupService(self)
def _get_headers(self, instance_token: str = None):
return {
'apikey': instance_token or self.api_token,
'Content-Type': 'application/json'
}
def _get_full_url(self, endpoint):
return f'{self.base_url}/{endpoint}'
def _handle_response(self, response):
if response.status_code == 401:
raise EvolutionAuthenticationError('Falha na autenticação.')
elif response.status_code == 404:
raise EvolutionNotFoundError('Recurso não encontrado.')
elif response.ok:
try:
return response.json()
except ValueError:
return response.content
else:
error_detail = ''
try:
error_detail = f' - {response.json()}'
except:
error_detail = f' - {response.text}'
raise EvolutionAPIError(f'Erro na requisição: {response.status_code}{error_detail}')
def get(self, endpoint: str, instance_token: str = None):
"""Faz uma requisição GET."""
url = self._get_full_url(endpoint)
response = requests.get(url, headers=self._get_headers(instance_token))
return self._handle_response(response)
def post(self, endpoint: str, data: dict = None, instance_token: str = None):
"""Faz uma requisição POST."""
url = self._get_full_url(endpoint)
response = requests.post(url, headers=self._get_headers(instance_token), json=data)
return self._handle_response(response)
def put(self, endpoint, data=None):
"""Faz uma requisição PUT."""
url = self._get_full_url(endpoint)
response = requests.put(url, headers=self.headers, json=data)
return self._handle_response(response)
def delete(self, endpoint: str, instance_token: str = None):
"""Faz uma requisição DELETE."""
url = self._get_full_url(endpoint)
response = requests.delete(url, headers=self._get_headers(instance_token))
return self._handle_response(response)

View File

@ -1,11 +0,0 @@
class EvolutionAPIError(Exception):
"""Erro genérico da API Evolution."""
pass
class EvolutionAuthenticationError(EvolutionAPIError):
"""Erro de autenticação com a API Evolution."""
pass
class EvolutionNotFoundError(EvolutionAPIError):
"""Recurso não encontrado na API Evolution."""
pass

View File

@ -1,16 +0,0 @@
class BaseCall:
def __init__(self, **kwargs):
self.__dict__.update({k: v for k, v in kwargs.items() if v is not None})
class FakeCall(BaseCall):
def __init__(
self,
number: str,
isVideo: bool,
callDuration: int
):
super().__init__(
number=number,
isVideo=isVideo,
callDuration=callDuration
)

View File

@ -1,93 +0,0 @@
from typing import List, Optional, Dict, Any
class BaseChat:
def __init__(self, **kwargs):
self.__dict__.update({k: v for k, v in kwargs.items() if v is not None})
class CheckIsWhatsappNumber(BaseChat):
def __init__(
self,
numbers: List[str]
):
super().__init__(
numbers=numbers
)
class MessageKey:
def __init__(
self,
remote_jid: str,
from_me: bool,
id: str,
participant: Optional[str] = None
):
self.remoteJid = remote_jid
self.fromMe = from_me
self.id = id
self.participant = participant
class ReadMessage:
def __init__(
self,
remote_jid: str,
from_me: bool,
id: str
):
self.remoteJid = remote_jid
self.fromMe = from_me
self.id = id
class ArchiveChat:
def __init__(
self,
last_message: Dict[str, Any],
chat: str,
archive: bool
):
self.lastMessage = last_message
self.chat = chat
self.archive = archive
class UnreadChat:
def __init__(
self,
last_message: Dict[str, Any],
chat: str
):
self.lastMessage = last_message
self.chat = chat
class ProfilePicture:
def __init__(self, number: str):
self.number = number
class MediaMessage:
def __init__(
self,
message: Dict[str, Any],
convert_to_mp4: bool = False
):
self.message = message
self.convertToMp4 = convert_to_mp4
class UpdateMessage:
def __init__(
self,
number: str,
key: Dict[str, Any],
text: str
):
self.number = number
self.key = key
self.text = text
class Presence:
def __init__(
self,
number: str,
delay: int,
presence: str
):
self.number = number
self.delay = delay
self.presence = presence

View File

@ -1,39 +0,0 @@
from typing import List, Optional, Literal
from dataclasses import dataclass
@dataclass
class CreateGroup:
subject: str
participants: List[str]
description: Optional[str] = None
@dataclass
class GroupPicture:
image: str
@dataclass
class GroupSubject:
subject: str
@dataclass
class GroupDescription:
description: str
@dataclass
class GroupInvite:
groupJid: str
description: str
numbers: List[str]
@dataclass
class UpdateParticipant:
action: Literal["add", "remove", "promote", "demote"]
participants: List[str]
@dataclass
class UpdateSetting:
action: Literal["announcement", "not_announcement", "locked", "unlocked"]
@dataclass
class ToggleEphemeral:
expiration: int

View File

@ -1,59 +0,0 @@
from typing import Optional, List, Dict
class WebhookConfig:
def __init__(self, url: str = None, byEvents: bool = False, base64: bool = True,
headers: Dict = None, events: List[str] = None):
self.url = url
self.byEvents = byEvents
self.base64 = base64
self.headers = headers
self.events = events
class EventsConfig:
def __init__(self, enabled: bool = True, events: List[str] = None):
self.enabled = enabled
self.events = events
class ChatwootConfig:
def __init__(self, accountId: str = None, token: str = None, url: str = None,
signMsg: bool = True, reopenConversation: bool = True,
conversationPending: bool = False, importContacts: bool = True,
nameInbox: str = "evolution", mergeBrazilContacts: bool = True,
importMessages: bool = True, daysLimitImportMessages: int = 3,
organization: str = "Evolution Bot",
logo: str = "https://evolution-api.com/files/evolution-api-favicon.png"):
self.chatwootAccountId = accountId
self.chatwootToken = token
self.chatwootUrl = url
self.chatwootSignMsg = signMsg
self.chatwootReopenConversation = reopenConversation
self.chatwootConversationPending = conversationPending
self.chatwootImportContacts = importContacts
self.chatwootNameInbox = nameInbox
self.chatwootMergeBrazilContacts = mergeBrazilContacts
self.chatwootImportMessages = importMessages
self.chatwootDaysLimitImportMessages = daysLimitImportMessages
self.chatwootOrganization = organization
self.chatwootLogo = logo
class InstanceConfig:
def __init__(
self,
instanceName: str,
integration: str = None,
token: str = None,
number: str = None,
qrcode: bool = None,
rejectCall: bool = None,
msgCall: str = None,
groupsIgnore: bool = None,
alwaysOnline: bool = None,
readMessages: bool = None,
readStatus: bool = None,
syncFullHistory: bool = None
):
self.__dict__['instanceName'] = instanceName
for key, value in locals().items():
if key != 'self' and key != 'instanceName' and value is not None:
self.__dict__[key] = value

View File

@ -1,21 +0,0 @@
from typing import Literal
class BaseLabel:
def __init__(self, **kwargs):
self.__dict__.update({k: v for k, v in kwargs.items() if v is not None})
class HandleLabel(BaseLabel):
def __init__(
self,
number: str,
label_id: str,
action: Literal["add", "remove"]
):
if action not in ["add", "remove"]:
raise ValueError("action deve ser 'add' ou 'remove'")
super().__init__(
number=number,
labelId=label_id,
action=action
)

View File

@ -1,254 +0,0 @@
from enum import Enum
from typing import List, Optional, Union
from dataclasses import dataclass
class MediaType(Enum):
IMAGE = "image"
VIDEO = "video"
DOCUMENT = "document"
class StatusType(Enum):
TEXT = "text"
IMAGE = "image"
VIDEO = "video"
AUDIO = "audio"
class FontType(Enum):
SERIF = 1
NORICAN_REGULAR = 2
BRYNDAN_WRITE = 3
BEBASNEUE_REGULAR = 4
OSWALD_HEAVY = 5
class BaseMessage:
def __init__(self, **kwargs):
self.__dict__.update({k: v for k, v in kwargs.items() if v is not None})
class QuotedMessage(BaseMessage):
def __init__(self, key: dict, message: Optional[dict] = None):
super().__init__(key=key, message=message)
class TextMessage(BaseMessage):
def __init__(
self,
number: str,
text: str,
delay: Optional[int] = None,
quoted: Optional[QuotedMessage] = None,
linkPreview: Optional[bool] = None,
mentionsEveryOne: Optional[bool] = None,
mentioned: Optional[List[str]] = None
):
super().__init__(
number=number,
text=text,
delay=delay,
quoted=quoted.__dict__ if quoted else None,
linkPreview=linkPreview,
mentionsEveryOne=mentionsEveryOne,
mentioned=mentioned
)
class MediaMessage(BaseMessage):
def __init__(
self,
number: str,
mediatype: str,
mimetype: str,
caption: str,
media: str,
fileName: str,
delay: Optional[int] = None,
quoted: Optional[QuotedMessage] = None,
mentionsEveryOne: Optional[bool] = None,
mentioned: Optional[List[str]] = None
):
super().__init__(
number=number,
mediatype=mediatype,
mimetype=mimetype,
caption=caption,
media=media,
fileName=fileName,
delay=delay,
quoted=quoted.__dict__ if quoted else None,
mentionsEveryOne=mentionsEveryOne,
mentioned=mentioned
)
class StatusMessage(BaseMessage):
def __init__(
self,
type: StatusType,
content: str,
caption: Optional[str] = None,
backgroundColor: Optional[str] = None,
font: Optional[FontType] = None,
allContacts: bool = False,
statusJidList: Optional[List[str]] = None
):
super().__init__(
type=type.value,
content=content,
caption=caption,
backgroundColor=backgroundColor,
font=font.value if font else None,
allContacts=allContacts,
statusJidList=statusJidList
)
class LocationMessage(BaseMessage):
def __init__(
self,
number: str,
name: str,
address: str,
latitude: float,
longitude: float,
delay: Optional[int] = None,
quoted: Optional[QuotedMessage] = None
):
super().__init__(
number=number,
name=name,
address=address,
latitude=latitude,
longitude=longitude,
delay=delay,
quoted=quoted.__dict__ if quoted else None
)
class Contact(BaseMessage):
def __init__(
self,
fullName: str,
wuid: str,
phoneNumber: str,
organization: Optional[str] = None,
email: Optional[str] = None,
url: Optional[str] = None
):
super().__init__(
fullName=fullName,
wuid=wuid,
phoneNumber=phoneNumber,
organization=organization,
email=email,
url=url
)
class ContactMessage(BaseMessage):
def __init__(self, number: str, contact: List[Contact]):
super().__init__(
number=number,
contact=[c.__dict__ for c in contact]
)
class ReactionMessage(BaseMessage):
def __init__(self, key: dict, reaction: str):
super().__init__(key=key, reaction=reaction)
class PollMessage(BaseMessage):
def __init__(
self,
number: str,
name: str,
selectableCount: int,
values: List[str],
delay: Optional[int] = None,
quoted: Optional[QuotedMessage] = None
):
super().__init__(
number=number,
name=name,
selectableCount=selectableCount,
values=values,
delay=delay,
quoted=quoted.__dict__ if quoted else None
)
class ListRow(BaseMessage):
def __init__(self, title: str, description: str, rowId: str):
super().__init__(
title=title,
description=description,
rowId=rowId
)
class ListSection(BaseMessage):
def __init__(self, title: str, rows: List[ListRow]):
super().__init__(
title=title,
rows=[r.__dict__ for r in rows]
)
class ListMessage(BaseMessage):
def __init__(
self,
number: str,
title: str,
description: str,
buttonText: str,
footerText: str,
sections: List[ListSection],
delay: Optional[int] = None,
quoted: Optional[QuotedMessage] = None
):
super().__init__(
number=number,
title=title,
description=description,
buttonText=buttonText,
footerText=footerText,
sections=[s.__dict__ for s in sections],
delay=delay,
quoted=quoted.__dict__ if quoted else None
)
class Button(BaseMessage):
def __init__(
self,
type: str,
displayText: str,
id: Optional[str] = None,
copyCode: Optional[str] = None,
url: Optional[str] = None,
phoneNumber: Optional[str] = None,
currency: Optional[str] = None,
name: Optional[str] = None,
keyType: Optional[str] = None,
key: Optional[str] = None
):
super().__init__(
type=type,
displayText=displayText,
id=id,
copyCode=copyCode,
url=url,
phoneNumber=phoneNumber,
currency=currency,
name=name,
keyType=keyType,
key=key
)
class ButtonMessage(BaseMessage):
def __init__(
self,
number: str,
title: str,
description: str,
footer: str,
buttons: List[Button],
delay: Optional[int] = None,
quoted: Optional[QuotedMessage] = None
):
super().__init__(
number=number,
title=title,
description=description,
footer=footer,
buttons=[b.__dict__ for b in buttons],
delay=delay,
quoted=quoted.__dict__ if quoted else None
)

View File

@ -1,9 +0,0 @@
from enum import Enum
class PresenceStatus(Enum):
AVAILABLE = "available"
UNAVAILABLE = "unavailable"
class PresenceConfig:
def __init__(self, presence: PresenceStatus):
self.presence = presence.value

View File

@ -1,60 +0,0 @@
from typing import Literal
class BaseProfile:
def __init__(self, **kwargs):
self.__dict__.update({k: v for k, v in kwargs.items() if v is not None})
class FetchProfile(BaseProfile):
def __init__(
self,
number: str,
):
super().__init__(
number=number,
)
class ProfileName(BaseProfile):
def __init__(
self,
name: str,
):
super().__init__(
name=name,
)
class ProfileStatus(BaseProfile):
def __init__(
self,
status: str,
):
super().__init__(
status=status,
)
class ProfilePicture(BaseProfile):
def __init__(
self,
picture: str,
):
super().__init__(
picture=picture,
)
class PrivacySettings(BaseProfile):
def __init__(
self,
readreceipts: Literal["all", "none"],
profile: Literal["all", "contacts", "contact_blacklist", "none"],
status: Literal["all", "contacts", "contact_blacklist", "none"],
online: Literal["all", "match_last_seen"],
last: Literal["all", "contacts", "contact_blacklist", "none"],
groupadd: Literal["all", "contacts", "contact_blacklist"],
):
super().__init__(
readreceipts=readreceipts,
profile=profile,
status=status,
online=online,
last=last,
groupadd=groupadd,
)

View File

@ -1,13 +0,0 @@
from typing import Union, BinaryIO
from ..models.call import *
class CallService:
def __init__(self, client):
self.client = client
def fake_call(self, instance_id: str, data: FakeCall, instance_token: str):
return self.client.post(
f'call/offer/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)

View File

@ -1,69 +0,0 @@
from typing import Union, BinaryIO
from ..models.chat import *
class ChatService:
def __init__(self, client):
self.client = client
def check_is_whatsapp_numbers(self, instance_id: str, data: CheckIsWhatsappNumber, instance_token: str):
return self.client.post(
f'chat/checkIsWhatsappNumber/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def mark_message_as_read(self, instance_id: str, messages: List[ReadMessage], instance_token: str):
return self.client.post(
f'chat/markMessageAsRead/{instance_id}',
data={"readMessages": [m.__dict__ for m in messages]},
instance_token=instance_token
)
def archive_chat(self, instance_id: str, data: ArchiveChat, instance_token: str):
return self.client.post(
f'chat/archiveChat/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def mark_chat_unread(self, instance_id: str, data: UnreadChat, instance_token: str):
return self.client.post(
f'chat/markChatUnread/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def delete_message_for_everyone(self, instance_id: str, data: MessageKey, instance_token: str):
return self.client.delete(
f'chat/deleteMessageForEveryone/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def fetch_profile_picture_url(self, instance_id: str, data: ProfilePicture, instance_token: str):
return self.client.post(
f'chat/fetchProfilePictureUrl/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def get_base64_from_media_message(self, instance_id: str, data: MediaMessage, instance_token: str):
return self.client.post(
f'chat/getBase64FromMediaMessage/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def update_message(self, instance_id: str, data: UpdateMessage, instance_token: str):
return self.client.post(
f'chat/updateMessage/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def send_presence(self, instance_id: str, data: Presence, instance_token: str):
return self.client.post(
f'chat/sendPresence/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)

View File

@ -1,117 +0,0 @@
from typing import Optional
from ..models.group import *
class GroupService:
def __init__(self, client):
self.client = client
def create_group(self, instance_id: str, data: CreateGroup, instance_token: str):
return self.client.post(
f'group/create/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def update_group_picture(self, instance_id: str, group_jid: str, data: GroupPicture, instance_token: str):
return self.client.post(
f'group/updateGroupPicture/{instance_id}',
params={'groupJid': group_jid},
data=data.__dict__,
instance_token=instance_token
)
def update_group_subject(self, instance_id: str, group_jid: str, data: GroupSubject, instance_token: str):
return self.client.post(
f'group/updateGroupSubject/{instance_id}',
params={'groupJid': group_jid},
data=data.__dict__,
instance_token=instance_token
)
def update_group_description(self, instance_id: str, group_jid: str, data: GroupDescription, instance_token: str):
return self.client.post(
f'group/updateGroupDescription/{instance_id}',
params={'groupJid': group_jid},
data=data.__dict__,
instance_token=instance_token
)
def get_invite_code(self, instance_id: str, group_jid: str, instance_token: str):
return self.client.get(
f'group/inviteCode/{instance_id}',
params={'groupJid': group_jid},
instance_token=instance_token
)
def revoke_invite_code(self, instance_id: str, group_jid: str, instance_token: str):
return self.client.post(
f'group/revokeInviteCode/{instance_id}',
params={'groupJid': group_jid},
instance_token=instance_token
)
def send_invite(self, instance_id: str, data: GroupInvite, instance_token: str):
return self.client.post(
f'group/sendInvite/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def get_invite_info(self, instance_id: str, invite_code: str, instance_token: str):
return self.client.get(
f'group/inviteInfo/{instance_id}',
params={'inviteCode': invite_code},
instance_token=instance_token
)
def get_group_info(self, instance_id: str, group_jid: str, instance_token: str):
return self.client.get(
f'group/findGroupInfos/{instance_id}',
params={'groupJid': group_jid},
instance_token=instance_token
)
def fetch_all_groups(self, instance_id: str, instance_token: str, get_participants: bool = False):
return self.client.get(
f'group/fetchAllGroups/{instance_id}',
params={'getParticipants': get_participants},
instance_token=instance_token
)
def get_participants(self, instance_id: str, group_jid: str, instance_token: str):
return self.client.get(
f'group/participants/{instance_id}',
params={'groupJid': group_jid},
instance_token=instance_token
)
def update_participant(self, instance_id: str, group_jid: str, data: UpdateParticipant, instance_token: str):
return self.client.post(
f'group/updateParticipant/{instance_id}',
params={'groupJid': group_jid},
data=data.__dict__,
instance_token=instance_token
)
def update_setting(self, instance_id: str, group_jid: str, data: UpdateSetting, instance_token: str):
return self.client.post(
f'group/updateSetting/{instance_id}',
params={'groupJid': group_jid},
data=data.__dict__,
instance_token=instance_token
)
def toggle_ephemeral(self, instance_id: str, group_jid: str, data: ToggleEphemeral, instance_token: str):
return self.client.post(
f'group/toggleEphemeral/{instance_id}',
params={'groupJid': group_jid},
data=data.__dict__,
instance_token=instance_token
)
def leave_group(self, instance_id: str, group_jid: str, instance_token: str):
return self.client.delete(
f'group/leaveGroup/{instance_id}',
params={'groupJid': group_jid},
instance_token=instance_token
)

View File

@ -1,9 +0,0 @@
class InstanceService:
def __init__(self, client):
self.client = client
def fetch_instances(self):
return self.client.get('instance/fetchInstances')
def create_instance(self, config):
return self.client.post('instance/create', data=config.__dict__)

View File

@ -1,28 +0,0 @@
from ..models.presence import PresenceStatus, PresenceConfig
class InstanceOperationsService:
def __init__(self, client):
self.client = client
def connect(self, instance_id: str, instance_token: str):
return self.client.get(f'instance/connect/{instance_id}', instance_token)
def restart(self, instance_id: str, instance_token: str):
return self.client.post(f'instance/restart/{instance_id}', instance_token=instance_token)
def set_presence(self, instance_id: str, presence: PresenceStatus, instance_token: str):
config = PresenceConfig(presence)
return self.client.post(
f'instance/setPresence/{instance_id}',
data=config.__dict__,
instance_token=instance_token
)
def get_connection_state(self, instance_id: str, instance_token: str):
return self.client.get(f'instance/connectionState/{instance_id}', instance_token)
def logout(self, instance_id: str, instance_token: str):
return self.client.delete(f'instance/logout/{instance_id}', instance_token)
def delete(self, instance_id: str, instance_token: str):
return self.client.delete(f'instance/delete/{instance_id}', instance_token)

View File

@ -1,19 +0,0 @@
from typing import Union, BinaryIO
from ..models.label import *
class LabelService:
def __init__(self, client):
self.client = client
def find_labels(self, instance_id: str, instance_token: str):
return self.client.get(
f'label/findLabels/{instance_id}',
instance_token=instance_token
)
def handle_label(self, instance_id: str, data: HandleLabel, instance_token: str):
return self.client.post(
f'label/handleLabel/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)

View File

@ -1,111 +0,0 @@
from typing import Union, BinaryIO
from ..models.message import *
class MessageService:
def __init__(self, client):
self.client = client
def send_text(self, instance_id: str, message: TextMessage, instance_token: str):
return self.client.post(
f'message/sendText/{instance_id}',
data=message.__dict__,
instance_token=instance_token
)
def send_media(self, instance_id: str, message: MediaMessage, instance_token: str, file: BinaryIO = None):
payload = {
'data': message.__dict__,
'instance_token': instance_token
}
if file:
payload['files'] = {'file': file}
return self.client.post(
f'message/sendMedia/{instance_id}',
**payload
)
def send_ptv(self, instance_id: str, message: dict, instance_token: str, file: BinaryIO = None):
payload = {
'data': message,
'instance_token': instance_token
}
if file:
payload['files'] = {'file': file}
return self.client.post(
f'message/sendPtv/{instance_id}',
**payload
)
def send_whatsapp_audio(self, instance_id: str, message: dict, instance_token: str, file: BinaryIO = None):
payload = {
'data': message,
'instance_token': instance_token
}
if file:
payload['files'] = {'file': file}
return self.client.post(
f'message/sendWhatsAppAudio/{instance_id}',
**payload
)
def send_status(self, instance_id: str, message: StatusMessage, instance_token: str):
return self.client.post(
f'message/sendStatus/{instance_id}',
data=message.__dict__,
instance_token=instance_token
)
def send_sticker(self, instance_id: str, message: dict, instance_token: str):
return self.client.post(
f'message/sendSticker/{instance_id}',
data=message,
instance_token=instance_token
)
def send_location(self, instance_id: str, message: LocationMessage, instance_token: str):
return self.client.post(
f'message/sendLocation/{instance_id}',
data=message.__dict__,
instance_token=instance_token
)
def send_contact(self, instance_id: str, message: ContactMessage, instance_token: str):
return self.client.post(
f'message/sendContact/{instance_id}',
data=message.__dict__,
instance_token=instance_token
)
def send_reaction(self, instance_id: str, message: ReactionMessage, instance_token: str):
return self.client.post(
f'message/sendReaction/{instance_id}',
data=message.__dict__,
instance_token=instance_token
)
def send_poll(self, instance_id: str, message: PollMessage, instance_token: str):
return self.client.post(
f'message/sendPoll/{instance_id}',
data=message.__dict__,
instance_token=instance_token
)
def send_list(self, instance_id: str, message: ListMessage, instance_token: str):
return self.client.post(
f'message/sendList/{instance_id}',
data=message.__dict__,
instance_token=instance_token
)
def send_buttons(self, instance_id: str, message: ButtonMessage, instance_token: str):
return self.client.post(
f'message/sendButtons/{instance_id}',
data=message.__dict__,
instance_token=instance_token
)

View File

@ -1,60 +0,0 @@
from typing import Union, BinaryIO
from ..models.profile import *
class ProfileService:
def __init__(self, client):
self.client = client
def fetch_business_profile(self, instance_id: str, data: FetchProfile, instance_token: str):
return self.client.post(
f'chat/fetchBusinessProfile/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def fetch_profile(self, instance_id: str, data: FetchProfile, instance_token: str):
return self.client.post(
f'chat/fetchProfile/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def update_profile_name(self, instance_id: str, data: ProfileName, instance_token: str):
return self.client.post(
f'chat/updateProfileName/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def update_profile_status(self, instance_id: str, data: ProfileStatus, instance_token: str):
return self.client.post(
f'chat/updateProfileStatus/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def update_profile_picture(self, instance_id: str, data: ProfilePicture, instance_token: str):
return self.client.post(
f'chat/updateProfilePicture/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def remove_profile_picture(self, instance_id: str, instance_token: str):
return self.client.delete(
f'chat/removeProfilePicture/{instance_id}',
instance_token=instance_token
)
def fetch_privacy_settings(self, instance_id: str, instance_token: str):
return self.client.get(
f'chat/fetchPrivacySettings/{instance_id}',
instance_token=instance_token
)
def update_privacy_settings(self, instance_id: str, data: PrivacySettings, instance_token: str):
return self.client.post(
f'chat/updatePrivacySettings/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)

View File

@ -1,138 +0,0 @@
import requests
from requests_toolbelt import MultipartEncoder
from .exceptions import EvolutionAuthenticationError, EvolutionNotFoundError, EvolutionAPIError
from .services.instance import InstanceService
from .services.instance_operations import InstanceOperationsService
from .services.message import MessageService
from .services.call import CallService
from .services.chat import ChatService
from .services.label import LabelService
from .services.profile import ProfileService
from .services.group import GroupService
from .services.websocket import WebSocketService, WebSocketManager
class EvolutionClient:
"""
Cliente para interagir com a API Evolution.
Args:
base_url (str): A URL base do servidor da API Evolution.
api_token (str): O token de autenticação para acessar a API.
"""
def __init__(self, base_url: str, api_token: str):
self.base_url = base_url.rstrip('/')
self.api_token = api_token
self.instances = InstanceService(self)
self.instance_operations = InstanceOperationsService(self)
self.messages = MessageService(self)
self.calls = CallService(self)
self.chat = ChatService(self)
self.label = LabelService(self)
self.profile = ProfileService(self)
self.group = GroupService(self)
self.websocket = WebSocketService(self)
def _get_headers(self, instance_token: str = None):
return {
'apikey': instance_token or self.api_token,
'Content-Type': 'application/json'
}
def _get_full_url(self, endpoint):
return f'{self.base_url}/{endpoint}'
def _handle_response(self, response):
if response.status_code == 401:
raise EvolutionAuthenticationError('Falha na autenticação.')
elif response.status_code == 404:
raise EvolutionNotFoundError('Recurso não encontrado.')
elif response.ok:
try:
return response.json()
except ValueError:
return response.content
else:
error_detail = ''
try:
error_detail = f' - {response.json()}'
except:
error_detail = f' - {response.text}'
raise EvolutionAPIError(f'Erro na requisição: {response.status_code}{error_detail}')
def get(self, endpoint: str, instance_token: str = None):
"""Faz uma requisição GET."""
url = self._get_full_url(endpoint)
response = requests.get(url, headers=self._get_headers(instance_token))
return self._handle_response(response)
def post(self, endpoint: str, data: dict = None, instance_token: str = None, files: dict = None):
url = f'{self.base_url}/{endpoint}'
headers = self._get_headers(instance_token)
if files:
# Remove o Content-Type do header quando enviando arquivos
if 'Content-Type' in headers:
del headers['Content-Type']
# Prepara os campos do multipart
fields = {}
# Adiciona os campos do data
for key, value in data.items():
fields[key] = str(value) if not isinstance(value, (int, float)) else (None, str(value), 'text/plain')
# Adiciona o arquivo
file_tuple = files['file']
fields['file'] = (file_tuple[0], file_tuple[1], file_tuple[2])
# Cria o multipart encoder
multipart = MultipartEncoder(fields=fields)
headers['Content-Type'] = multipart.content_type
response = requests.post(
url,
headers=headers,
data=multipart
)
else:
response = requests.post(
url,
headers=headers,
json=data
)
return response.json()
def put(self, endpoint, data=None):
"""Faz uma requisição PUT."""
url = self._get_full_url(endpoint)
response = requests.put(url, headers=self.headers, json=data)
return self._handle_response(response)
def delete(self, endpoint: str, instance_token: str = None):
"""Faz uma requisição DELETE."""
url = self._get_full_url(endpoint)
response = requests.delete(url, headers=self._get_headers(instance_token))
return self._handle_response(response)
def create_websocket(self, instance_id: str, api_token: str, max_retries: int = 5, retry_delay: float = 1.0) -> WebSocketManager:
"""
Create a WebSocket manager for the specified instance.
Args:
instance_id (str): The instance ID
api_token (str): The API token
max_retries (int): Maximum number of reconnection attempts
retry_delay (float): Initial delay between attempts in seconds
Returns:
WebSocketManager: The WebSocket manager instance
"""
return WebSocketManager(
base_url=self.base_url,
instance_id=instance_id,
api_token=api_token,
max_retries=max_retries,
retry_delay=retry_delay
)

View File

@ -1,11 +0,0 @@
class EvolutionAPIError(Exception):
"""Erro genérico da API Evolution."""
pass
class EvolutionAuthenticationError(EvolutionAPIError):
"""Erro de autenticação com a API Evolution."""
pass
class EvolutionNotFoundError(EvolutionAPIError):
"""Recurso não encontrado na API Evolution."""
pass

View File

@ -1,16 +0,0 @@
class BaseCall:
def __init__(self, **kwargs):
self.__dict__.update({k: v for k, v in kwargs.items() if v is not None})
class FakeCall(BaseCall):
def __init__(
self,
number: str,
isVideo: bool,
callDuration: int
):
super().__init__(
number=number,
isVideo=isVideo,
callDuration=callDuration
)

View File

@ -1,93 +0,0 @@
from typing import List, Optional, Dict, Any
class BaseChat:
def __init__(self, **kwargs):
self.__dict__.update({k: v for k, v in kwargs.items() if v is not None})
class CheckIsWhatsappNumber(BaseChat):
def __init__(
self,
numbers: List[str]
):
super().__init__(
numbers=numbers
)
class MessageKey:
def __init__(
self,
remote_jid: str,
from_me: bool,
id: str,
participant: Optional[str] = None
):
self.remoteJid = remote_jid
self.fromMe = from_me
self.id = id
self.participant = participant
class ReadMessage:
def __init__(
self,
remote_jid: str,
from_me: bool,
id: str
):
self.remoteJid = remote_jid
self.fromMe = from_me
self.id = id
class ArchiveChat:
def __init__(
self,
last_message: Dict[str, Any],
chat: str,
archive: bool
):
self.lastMessage = last_message
self.chat = chat
self.archive = archive
class UnreadChat:
def __init__(
self,
last_message: Dict[str, Any],
chat: str
):
self.lastMessage = last_message
self.chat = chat
class ProfilePicture:
def __init__(self, number: str):
self.number = number
class MediaMessage:
def __init__(
self,
message: Dict[str, Any],
convert_to_mp4: bool = False
):
self.message = message
self.convertToMp4 = convert_to_mp4
class UpdateMessage:
def __init__(
self,
number: str,
key: Dict[str, Any],
text: str
):
self.number = number
self.key = key
self.text = text
class Presence:
def __init__(
self,
number: str,
delay: int,
presence: str
):
self.number = number
self.delay = delay
self.presence = presence

View File

@ -1,39 +0,0 @@
from typing import List, Optional, Literal
from dataclasses import dataclass
@dataclass
class CreateGroup:
subject: str
participants: List[str]
description: Optional[str] = None
@dataclass
class GroupPicture:
image: str
@dataclass
class GroupSubject:
subject: str
@dataclass
class GroupDescription:
description: str
@dataclass
class GroupInvite:
groupJid: str
description: str
numbers: List[str]
@dataclass
class UpdateParticipant:
action: Literal["add", "remove", "promote", "demote"]
participants: List[str]
@dataclass
class UpdateSetting:
action: Literal["announcement", "not_announcement", "locked", "unlocked"]
@dataclass
class ToggleEphemeral:
expiration: int

View File

@ -1,59 +0,0 @@
from typing import Optional, List, Dict
class WebhookConfig:
def __init__(self, url: str = None, byEvents: bool = False, base64: bool = True,
headers: Dict = None, events: List[str] = None):
self.url = url
self.byEvents = byEvents
self.base64 = base64
self.headers = headers
self.events = events
class EventsConfig:
def __init__(self, enabled: bool = True, events: List[str] = None):
self.enabled = enabled
self.events = events
class ChatwootConfig:
def __init__(self, accountId: str = None, token: str = None, url: str = None,
signMsg: bool = True, reopenConversation: bool = True,
conversationPending: bool = False, importContacts: bool = True,
nameInbox: str = "evolution", mergeBrazilContacts: bool = True,
importMessages: bool = True, daysLimitImportMessages: int = 3,
organization: str = "Evolution Bot",
logo: str = "https://evolution-api.com/files/evolution-api-favicon.png"):
self.chatwootAccountId = accountId
self.chatwootToken = token
self.chatwootUrl = url
self.chatwootSignMsg = signMsg
self.chatwootReopenConversation = reopenConversation
self.chatwootConversationPending = conversationPending
self.chatwootImportContacts = importContacts
self.chatwootNameInbox = nameInbox
self.chatwootMergeBrazilContacts = mergeBrazilContacts
self.chatwootImportMessages = importMessages
self.chatwootDaysLimitImportMessages = daysLimitImportMessages
self.chatwootOrganization = organization
self.chatwootLogo = logo
class InstanceConfig:
def __init__(
self,
instanceName: str,
integration: str = None,
token: str = None,
number: str = None,
qrcode: bool = None,
rejectCall: bool = None,
msgCall: str = None,
groupsIgnore: bool = None,
alwaysOnline: bool = None,
readMessages: bool = None,
readStatus: bool = None,
syncFullHistory: bool = None
):
self.__dict__['instanceName'] = instanceName
for key, value in locals().items():
if key != 'self' and key != 'instanceName' and value is not None:
self.__dict__[key] = value

View File

@ -1,21 +0,0 @@
from typing import Literal
class BaseLabel:
def __init__(self, **kwargs):
self.__dict__.update({k: v for k, v in kwargs.items() if v is not None})
class HandleLabel(BaseLabel):
def __init__(
self,
number: str,
label_id: str,
action: Literal["add", "remove"]
):
if action not in ["add", "remove"]:
raise ValueError("action deve ser 'add' ou 'remove'")
super().__init__(
number=number,
labelId=label_id,
action=action
)

View File

@ -1,260 +0,0 @@
from enum import Enum
from typing import List, Optional, Union
from dataclasses import dataclass
class MediaType(Enum):
IMAGE = "image"
VIDEO = "video"
DOCUMENT = "document"
class StatusType(Enum):
TEXT = "text"
IMAGE = "image"
VIDEO = "video"
AUDIO = "audio"
class FontType(Enum):
SERIF = 1
NORICAN_REGULAR = 2
BRYNDAN_WRITE = 3
BEBASNEUE_REGULAR = 4
OSWALD_HEAVY = 5
class BaseMessage:
def __init__(self, **kwargs):
self.__dict__.update({k: v for k, v in kwargs.items() if v is not None})
class QuotedMessage(BaseMessage):
def __init__(self, key: dict, message: Optional[dict] = None):
super().__init__(key=key, message=message)
class TextMessage(BaseMessage):
def __init__(
self,
number: str,
text: str,
delay: Optional[int] = None,
quoted: Optional[QuotedMessage] = None,
linkPreview: Optional[bool] = None,
mentionsEveryOne: Optional[bool] = None,
mentioned: Optional[List[str]] = None
):
super().__init__(
number=number,
text=text,
delay=delay,
quoted=quoted.__dict__ if quoted else None,
linkPreview=linkPreview,
mentionsEveryOne=mentionsEveryOne,
mentioned=mentioned
)
class MediaMessage(BaseMessage):
def __init__(
self,
number: str,
media: dict = None,
mediatype: Optional[str] = None,
caption: str = None,
mimetype: str = None,
fileName: str = None,
delay: Optional[Union[int, float, str]] = None,
quoted: Optional[QuotedMessage] = None,
mentionsEveryOne: Optional[bool] = None,
mentioned: Optional[List[str]] = None
):
data = {
'number': number,
'mediatype': mediatype,
'caption': caption,
'mimetype': mimetype,
'fileName': fileName,
'quoted': quoted.__dict__ if quoted else None,
'mentionsEveryOne': mentionsEveryOne,
'mentioned': mentioned
}
if delay is not None:
data['delay'] = delay
if media and media != {}:
data['media'] = media
super().__init__(**{k: v for k, v in data.items() if v is not None})
class StatusMessage(BaseMessage):
def __init__(
self,
type: StatusType,
content: str,
caption: Optional[str] = None,
backgroundColor: Optional[str] = None,
font: Optional[FontType] = None,
allContacts: bool = False,
statusJidList: Optional[List[str]] = None
):
super().__init__(
type=type.value,
content=content,
caption=caption,
backgroundColor=backgroundColor,
font=font.value if font else None,
allContacts=allContacts,
statusJidList=statusJidList
)
class LocationMessage(BaseMessage):
def __init__(
self,
number: str,
name: str,
address: str,
latitude: float,
longitude: float,
delay: Optional[int] = None,
quoted: Optional[QuotedMessage] = None
):
super().__init__(
number=number,
name=name,
address=address,
latitude=latitude,
longitude=longitude,
delay=delay,
quoted=quoted.__dict__ if quoted else None
)
class Contact(BaseMessage):
def __init__(
self,
fullName: str,
wuid: str,
phoneNumber: str,
organization: Optional[str] = None,
email: Optional[str] = None,
url: Optional[str] = None
):
super().__init__(
fullName=fullName,
wuid=wuid,
phoneNumber=phoneNumber,
organization=organization,
email=email,
url=url
)
class ContactMessage(BaseMessage):
def __init__(self, number: str, contact: List[Contact]):
super().__init__(
number=number,
contact=[c.__dict__ for c in contact]
)
class ReactionMessage(BaseMessage):
def __init__(self, key: dict, reaction: str):
super().__init__(key=key, reaction=reaction)
class PollMessage(BaseMessage):
def __init__(
self,
number: str,
name: str,
selectableCount: int,
values: List[str],
delay: Optional[int] = None,
quoted: Optional[QuotedMessage] = None
):
super().__init__(
number=number,
name=name,
selectableCount=selectableCount,
values=values,
delay=delay,
quoted=quoted.__dict__ if quoted else None
)
class ListRow(BaseMessage):
def __init__(self, title: str, description: str, rowId: str):
super().__init__(
title=title,
description=description,
rowId=rowId
)
class ListSection(BaseMessage):
def __init__(self, title: str, rows: List[ListRow]):
super().__init__(
title=title,
rows=[r.__dict__ for r in rows]
)
class ListMessage(BaseMessage):
def __init__(
self,
number: str,
title: str,
description: str,
buttonText: str,
footerText: str,
sections: List[ListSection],
delay: Optional[int] = None,
quoted: Optional[QuotedMessage] = None
):
super().__init__(
number=number,
title=title,
description=description,
buttonText=buttonText,
footerText=footerText,
sections=[s.__dict__ for s in sections],
delay=delay,
quoted=quoted.__dict__ if quoted else None
)
class Button(BaseMessage):
def __init__(
self,
type: str,
displayText: str,
id: Optional[str] = None,
copyCode: Optional[str] = None,
url: Optional[str] = None,
phoneNumber: Optional[str] = None,
currency: Optional[str] = None,
name: Optional[str] = None,
keyType: Optional[str] = None,
key: Optional[str] = None
):
super().__init__(
type=type,
displayText=displayText,
id=id,
copyCode=copyCode,
url=url,
phoneNumber=phoneNumber,
currency=currency,
name=name,
keyType=keyType,
key=key
)
class ButtonMessage(BaseMessage):
def __init__(
self,
number: str,
title: str,
description: str,
footer: str,
buttons: List[Button],
delay: Optional[int] = None,
quoted: Optional[QuotedMessage] = None
):
super().__init__(
number=number,
title=title,
description=description,
footer=footer,
buttons=[b.__dict__ for b in buttons],
delay=delay,
quoted=quoted.__dict__ if quoted else None
)

View File

@ -1,9 +0,0 @@
from enum import Enum
class PresenceStatus(Enum):
AVAILABLE = "available"
UNAVAILABLE = "unavailable"
class PresenceConfig:
def __init__(self, presence: PresenceStatus):
self.presence = presence.value

View File

@ -1,60 +0,0 @@
from typing import Literal
class BaseProfile:
def __init__(self, **kwargs):
self.__dict__.update({k: v for k, v in kwargs.items() if v is not None})
class FetchProfile(BaseProfile):
def __init__(
self,
number: str,
):
super().__init__(
number=number,
)
class ProfileName(BaseProfile):
def __init__(
self,
name: str,
):
super().__init__(
name=name,
)
class ProfileStatus(BaseProfile):
def __init__(
self,
status: str,
):
super().__init__(
status=status,
)
class ProfilePicture(BaseProfile):
def __init__(
self,
picture: str,
):
super().__init__(
picture=picture,
)
class PrivacySettings(BaseProfile):
def __init__(
self,
readreceipts: Literal["all", "none"],
profile: Literal["all", "contacts", "contact_blacklist", "none"],
status: Literal["all", "contacts", "contact_blacklist", "none"],
online: Literal["all", "match_last_seen"],
last: Literal["all", "contacts", "contact_blacklist", "none"],
groupadd: Literal["all", "contacts", "contact_blacklist"],
):
super().__init__(
readreceipts=readreceipts,
profile=profile,
status=status,
online=online,
last=last,
groupadd=groupadd,
)

View File

@ -1,20 +0,0 @@
from typing import List, Optional
from dataclasses import dataclass
@dataclass
class WebSocketConfig:
enabled: bool
events: List[str]
def __init__(self, enabled: bool, events: List[str]):
self.enabled = enabled
self.events = events
@dataclass
class WebSocketInfo:
enabled: bool
events: List[str]
def __init__(self, **kwargs):
self.enabled = kwargs.get('enabled', False)
self.events = kwargs.get('events', [])

View File

@ -1,13 +0,0 @@
from typing import Union, BinaryIO
from ..models.call import *
class CallService:
def __init__(self, client):
self.client = client
def fake_call(self, instance_id: str, data: FakeCall, instance_token: str):
return self.client.post(
f'call/offer/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)

View File

@ -1,122 +0,0 @@
from typing import Union, BinaryIO, Optional
from ..models.chat import *
class ChatService:
def __init__(self, client):
self.client = client
def check_is_whatsapp_numbers(self, instance_id: str, data: CheckIsWhatsappNumber, instance_token: str):
return self.client.post(
f'chat/checkIsWhatsappNumber/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def mark_message_as_read(self, instance_id: str, messages: List[ReadMessage], instance_token: str):
return self.client.post(
f'chat/markMessageAsRead/{instance_id}',
data={"readMessages": [m.__dict__ for m in messages]},
instance_token=instance_token
)
def archive_chat(self, instance_id: str, data: ArchiveChat, instance_token: str):
return self.client.post(
f'chat/archiveChat/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def mark_chat_unread(self, instance_id: str, data: UnreadChat, instance_token: str):
return self.client.post(
f'chat/markChatUnread/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def delete_message_for_everyone(self, instance_id: str, data: MessageKey, instance_token: str):
return self.client.delete(
f'chat/deleteMessageForEveryone/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def fetch_profile_picture_url(self, instance_id: str, data: ProfilePicture, instance_token: str):
return self.client.post(
f'chat/fetchProfilePictureUrl/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def get_base64_from_media_message(self, instance_id: str, data: MediaMessage, instance_token: str):
return self.client.post(
f'chat/getBase64FromMediaMessage/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def update_message(self, instance_id: str, data: UpdateMessage, instance_token: str):
return self.client.post(
f'chat/updateMessage/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def send_presence(self, instance_id: str, data: Presence, instance_token: str):
return self.client.post(
f'chat/sendPresence/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def get_messages(
self,
instance_id: str,
remote_jid: str,
instance_token: str,
message_id: Optional[str] = None,
whatsapp_message_id: Optional[str] = None,
from_me: Optional[bool] = None,
message_type: Optional[str] = None,
source: Optional[str] = None,
timestamp_start: Optional[str] = None,
timestamp_end: Optional[str] = None,
page: int = 1,
offset: int = 50
):
'''
Obtém mensagens de um chat com filtros opcionais
Args:
timestamp_start: Data inicial no formato ISO (ex: "2025-01-16T00:00:00Z")
timestamp_end: Data final no formato ISO (ex: "2025-01-16T23:59:59Z")
'''
where = {"key": {"remoteJid": remote_jid}}
if message_id:
where["id"] = message_id
if whatsapp_message_id:
where["key"]["id"] = whatsapp_message_id
if from_me is not None:
where["key"]["fromMe"] = from_me
if message_type:
where["messageType"] = message_type
if source:
where["source"] = source
if timestamp_start or timestamp_end:
where["messageTimestamp"] = {}
if timestamp_start:
where["messageTimestamp"]["gte"] = timestamp_start
if timestamp_end:
where["messageTimestamp"]["lte"] = timestamp_end
payload = {
"where": where,
"page": page,
"offset": offset,
}
return self.client.post(
f'chat/findMessages/{instance_id}',
data=payload,
instance_token=instance_token,
)

View File

@ -1,105 +0,0 @@
from typing import Optional
from ..models.group import *
class GroupService:
def __init__(self, client):
self.client = client
def create_group(self, instance_id: str, data: CreateGroup, instance_token: str):
return self.client.post(
f'group/create/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def update_group_picture(self, instance_id: str, group_jid: str, data: GroupPicture, instance_token: str):
return self.client.post(
f'group/updateGroupPicture/{instance_id}?groupJid={group_jid}',
data=data.__dict__,
instance_token=instance_token
)
def update_group_subject(self, instance_id: str, group_jid: str, data: GroupSubject, instance_token: str):
return self.client.post(
f'group/updateGroupSubject/{instance_id}?groupJid={group_jid}',
data=data.__dict__,
instance_token=instance_token
)
def update_group_description(self, instance_id: str, group_jid: str, data: GroupDescription, instance_token: str):
return self.client.post(
f'group/updateGroupDescription/{instance_id}?groupJid={group_jid}',
data=data.__dict__,
instance_token=instance_token
)
def get_invite_code(self, instance_id: str, group_jid: str, instance_token: str):
return self.client.get(
f'group/inviteCode/{instance_id}?groupJid={group_jid}',
instance_token=instance_token
)
def revoke_invite_code(self, instance_id: str, group_jid: str, instance_token: str):
return self.client.post(
f'group/revokeInviteCode/{instance_id}?groupJid={group_jid}',
instance_token=instance_token
)
def send_invite(self, instance_id: str, data: GroupInvite, instance_token: str):
return self.client.post(
f'group/sendInvite/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def get_invite_info(self, instance_id: str, invite_code: str, instance_token: str):
return self.client.get(
f'group/inviteInfo/{instance_id}?inviteCode={invite_code}',
instance_token=instance_token
)
def get_group_info(self, instance_id: str, group_jid: str, instance_token: str):
return self.client.get(
f'group/findGroupInfos/{instance_id}?groupJid={group_jid}',
instance_token=instance_token
)
def fetch_all_groups(self, instance_id: str, instance_token: str, get_participants: bool = False):
url = f'group/fetchAllGroups/{instance_id}?getParticipants={str(get_participants).lower()}'
return self.client.get(
url,
instance_token=instance_token
)
def get_participants(self, instance_id: str, group_jid: str, instance_token: str):
return self.client.get(
f'group/participants/{instance_id}?groupJid={group_jid}',
instance_token=instance_token
)
def update_participant(self, instance_id: str, group_jid: str, data: UpdateParticipant, instance_token: str):
return self.client.post(
f'group/updateParticipant/{instance_id}?groupJid={group_jid}',
data=data.__dict__,
instance_token=instance_token
)
def update_setting(self, instance_id: str, group_jid: str, data: UpdateSetting, instance_token: str):
return self.client.post(
f'group/updateSetting/{instance_id}?groupJid={group_jid}',
data=data.__dict__,
instance_token=instance_token
)
def toggle_ephemeral(self, instance_id: str, group_jid: str, data: ToggleEphemeral, instance_token: str):
return self.client.post(
f'group/toggleEphemeral/{instance_id}?groupJid={group_jid}',
data=data.__dict__,
instance_token=instance_token
)
def leave_group(self, instance_id: str, group_jid: str, instance_token: str):
return self.client.delete(
f'group/leaveGroup/{instance_id}?groupJid={group_jid}',
instance_token=instance_token
)

View File

@ -1,9 +0,0 @@
class InstanceService:
def __init__(self, client):
self.client = client
def fetch_instances(self):
return self.client.get('instance/fetchInstances')
def create_instance(self, config):
return self.client.post('instance/create', data=config.__dict__)

View File

@ -1,28 +0,0 @@
from ..models.presence import PresenceStatus, PresenceConfig
class InstanceOperationsService:
def __init__(self, client):
self.client = client
def connect(self, instance_id: str, instance_token: str):
return self.client.get(f'instance/connect/{instance_id}', instance_token)
def restart(self, instance_id: str, instance_token: str):
return self.client.post(f'instance/restart/{instance_id}', instance_token=instance_token)
def set_presence(self, instance_id: str, presence: PresenceStatus, instance_token: str):
config = PresenceConfig(presence)
return self.client.post(
f'instance/setPresence/{instance_id}',
data=config.__dict__,
instance_token=instance_token
)
def get_connection_state(self, instance_id: str, instance_token: str):
return self.client.get(f'instance/connectionState/{instance_id}', instance_token)
def logout(self, instance_id: str, instance_token: str):
return self.client.delete(f'instance/logout/{instance_id}', instance_token)
def delete(self, instance_id: str, instance_token: str):
return self.client.delete(f'instance/delete/{instance_id}', instance_token)

View File

@ -1,19 +0,0 @@
from typing import Union, BinaryIO
from ..models.label import *
class LabelService:
def __init__(self, client):
self.client = client
def find_labels(self, instance_id: str, instance_token: str):
return self.client.get(
f'label/findLabels/{instance_id}',
instance_token=instance_token
)
def handle_label(self, instance_id: str, data: HandleLabel, instance_token: str):
return self.client.post(
f'label/handleLabel/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)

View File

@ -1,205 +0,0 @@
from typing import Union, BinaryIO
from ..models.message import *
from requests_toolbelt import MultipartEncoder
import mimetypes
import requests
class MessageService:
def __init__(self, client):
self.client = client
def send_text(self, instance_id: str, message: TextMessage, instance_token: str):
# Preparar os dados como JSON
data = {
'number': message.number,
'text': message.text
}
if hasattr(message, 'delay') and message.delay is not None:
data['delay'] = message.delay
# Usar o método post do cliente que já trata JSON corretamente
return self.client.post(
f'message/sendText/{instance_id}',
data=data,
instance_token=instance_token
)
def send_media(self, instance_id: str, message: MediaMessage, instance_token: str, file: Union[BinaryIO, str] = None):
# Preparar os dados do formulário
fields = {
'number': (None, message.number, 'text/plain'),
'mediatype': (None, message.mediatype, 'text/plain'),
'mimetype': (None, message.mimetype, 'text/plain'),
'caption': (None, message.caption, 'text/plain'),
'fileName': (None, message.fileName, 'text/plain'),
}
# Adicionar delay apenas se existir
if hasattr(message, 'delay') and message.delay is not None:
fields['delay'] = (None, str(message.delay), 'text/plain; type=number')
# Adicionar o arquivo se fornecido
if file:
if isinstance(file, str):
mime_type = mimetypes.guess_type(file)[0] or 'application/octet-stream'
fields['file'] = ('file', open(file, 'rb'), mime_type)
else:
fields['file'] = ('file', file, 'application/octet-stream')
# Criar o multipart encoder
multipart = MultipartEncoder(fields=fields)
# Preparar os headers
headers = self.client._get_headers(instance_token)
headers['Content-Type'] = multipart.content_type
# Fazer a requisição diretamente
url = f'{self.client.base_url}/message/sendMedia/{instance_id}'
response = requests.post(
url,
headers=headers,
data=multipart
)
return response.json()
def send_ptv(self, instance_id: str, message: dict, instance_token: str, file: Union[BinaryIO, str] = None):
fields = {}
# Adiciona todos os campos do message como text/plain
for key, value in message.items():
if key == 'delay' and value is not None:
fields[key] = (None, str(value), 'text/plain; type=number')
else:
fields[key] = (None, str(value), 'text/plain')
if file:
if isinstance(file, str):
mime_type = mimetypes.guess_type(file)[0] or 'application/octet-stream'
fields['file'] = ('file', open(file, 'rb'), mime_type)
else:
fields['file'] = ('file', file, 'application/octet-stream')
multipart = MultipartEncoder(fields=fields)
headers = self.client._get_headers(instance_token)
headers['Content-Type'] = multipart.content_type
url = f'{self.client.base_url}/message/sendPtv/{instance_id}'
response = requests.post(url, headers=headers, data=multipart)
return response.json()
def send_whatsapp_audio(self, instance_id: str, message: dict, instance_token: str, file: Union[BinaryIO, str] = None):
fields = {}
# Adiciona todos os campos do message como text/plain
for key, value in message.items():
if key == 'delay' and value is not None:
fields[key] = (None, str(value), 'text/plain; type=number')
else:
fields[key] = (None, str(value), 'text/plain')
if file:
if isinstance(file, str):
mime_type = mimetypes.guess_type(file)[0] or 'application/octet-stream'
fields['file'] = ('file', open(file, 'rb'), mime_type)
else:
fields['file'] = ('file', file, 'application/octet-stream')
multipart = MultipartEncoder(fields=fields)
headers = self.client._get_headers(instance_token)
headers['Content-Type'] = multipart.content_type
url = f'{self.client.base_url}/message/sendWhatsAppAudio/{instance_id}'
response = requests.post(url, headers=headers, data=multipart)
return response.json()
def send_status(self, instance_id: str, message: StatusMessage, instance_token: str):
return self.client.post(
f'message/sendStatus/{instance_id}',
data=message.__dict__,
instance_token=instance_token
)
def send_sticker(self, instance_id: str, message: dict, instance_token: str, file: Union[BinaryIO, str] = None):
fields = {}
# Adiciona todos os campos do message como text/plain
for key, value in message.items():
if key == 'delay' and value is not None:
fields[key] = (None, str(value), 'text/plain; type=number')
else:
fields[key] = (None, str(value), 'text/plain')
if file:
if isinstance(file, str):
mime_type = mimetypes.guess_type(file)[0] or 'application/octet-stream'
fields['file'] = ('file', open(file, 'rb'), mime_type)
else:
fields['file'] = ('file', file, 'application/octet-stream')
multipart = MultipartEncoder(fields=fields)
headers = self.client._get_headers(instance_token)
headers['Content-Type'] = multipart.content_type
url = f'{self.client.base_url}/message/sendSticker/{instance_id}'
response = requests.post(url, headers=headers, data=multipart)
return response.json()
def send_location(self, instance_id: str, message: LocationMessage, instance_token: str):
data = message.__dict__.copy()
if 'delay' in data and data['delay'] is not None:
data['delay'] = int(data['delay'])
return self.client.post(
f'message/sendLocation/{instance_id}',
data=data,
instance_token=instance_token
)
def send_contact(self, instance_id: str, message: ContactMessage, instance_token: str):
return self.client.post(
f'message/sendContact/{instance_id}',
data=message.__dict__,
instance_token=instance_token
)
def send_reaction(self, instance_id: str, message: ReactionMessage, instance_token: str):
return self.client.post(
f'message/sendReaction/{instance_id}',
data=message.__dict__,
instance_token=instance_token
)
def send_poll(self, instance_id: str, message: PollMessage, instance_token: str):
data = message.__dict__.copy()
if 'delay' in data and data['delay'] is not None:
data['delay'] = int(data['delay'])
return self.client.post(
f'message/sendPoll/{instance_id}',
data=data,
instance_token=instance_token
)
def send_list(self, instance_id: str, message: ListMessage, instance_token: str):
data = message.__dict__.copy()
if 'delay' in data and data['delay'] is not None:
data['delay'] = int(data['delay'])
return self.client.post(
f'message/sendList/{instance_id}',
data=data,
instance_token=instance_token
)
def send_buttons(self, instance_id: str, message: ButtonMessage, instance_token: str):
data = message.__dict__.copy()
if 'delay' in data and data['delay'] is not None:
data['delay'] = int(data['delay'])
return self.client.post(
f'message/sendButtons/{instance_id}',
data=data,
instance_token=instance_token
)

View File

@ -1,60 +0,0 @@
from typing import Union, BinaryIO
from ..models.profile import *
class ProfileService:
def __init__(self, client):
self.client = client
def fetch_business_profile(self, instance_id: str, data: FetchProfile, instance_token: str):
return self.client.post(
f'chat/fetchBusinessProfile/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def fetch_profile(self, instance_id: str, data: FetchProfile, instance_token: str):
return self.client.post(
f'chat/fetchProfile/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def update_profile_name(self, instance_id: str, data: ProfileName, instance_token: str):
return self.client.post(
f'chat/updateProfileName/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def update_profile_status(self, instance_id: str, data: ProfileStatus, instance_token: str):
return self.client.post(
f'chat/updateProfileStatus/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def update_profile_picture(self, instance_id: str, data: ProfilePicture, instance_token: str):
return self.client.post(
f'chat/updateProfilePicture/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)
def remove_profile_picture(self, instance_id: str, instance_token: str):
return self.client.delete(
f'chat/removeProfilePicture/{instance_id}',
instance_token=instance_token
)
def fetch_privacy_settings(self, instance_id: str, instance_token: str):
return self.client.get(
f'chat/fetchPrivacySettings/{instance_id}',
instance_token=instance_token
)
def update_privacy_settings(self, instance_id: str, data: PrivacySettings, instance_token: str):
return self.client.post(
f'chat/updatePrivacySettings/{instance_id}',
data=data.__dict__,
instance_token=instance_token
)

View File

@ -1,174 +0,0 @@
import socketio
from typing import Callable, Dict, Any
import logging
import time
from typing import Optional
from ..models.websocket import WebSocketConfig, WebSocketInfo
class WebSocketService:
def __init__(self, client):
self.client = client
def set_websocket(self, instance_id: str, config: WebSocketConfig, instance_token: str):
"""
Configure WebSocket settings for an instance
Args:
instance_id (str): The instance ID
config (WebSocketConfig): The WebSocket configuration
instance_token (str): The instance token
Returns:
dict: The response from the API
"""
return self.client.post(
f'websocket/set/{instance_id}',
data=config.__dict__,
instance_token=instance_token
)
def find_websocket(self, instance_id: str, instance_token: str) -> WebSocketInfo:
"""
Get WebSocket settings for an instance
Args:
instance_id (str): The instance ID
instance_token (str): The instance token
Returns:
WebSocketInfo: The WebSocket information
"""
response = self.client.get(
f'websocket/find/{instance_id}',
instance_token=instance_token
)
return WebSocketInfo(**response)
class WebSocketManager:
def __init__(self, base_url: str, instance_id: str, api_token: str, max_retries: int = 5, retry_delay: float = 1.0):
"""
Initialize the WebSocket manager
Args:
base_url (str): Base URL of the API
instance_id (str): Instance ID
api_token (str): API authentication token
max_retries (int): Maximum number of reconnection attempts
retry_delay (float): Initial delay between attempts in seconds
"""
self.base_url = base_url.rstrip('/')
self.instance_id = instance_id
self.api_token = api_token
self.max_retries = max_retries
self.retry_delay = retry_delay
self.retry_count = 0
self.should_reconnect = True
# Socket.IO configuration
self.sio = socketio.Client(
ssl_verify=False, # For local development
logger=False,
engineio_logger=False,
request_timeout=30
)
# Configure class logger to INFO
self.logger = logging.getLogger(__name__)
self.logger.setLevel(logging.INFO)
# Dictionary to store registered handlers
self._handlers = {}
# Configure event handlers
self.sio.on('connect', self._on_connect)
self.sio.on('disconnect', self._on_disconnect)
self.sio.on('error', self._on_error)
# Register global handler in instance-specific namespace
self.sio.on('*', self._handle_event, namespace=f'/{self.instance_id}')
def _on_connect(self):
"""Handler for connection event"""
self.logger.info("Socket.IO connected")
self.retry_count = 0 # Reset retry counter after successful connection
def _on_disconnect(self):
"""Handler for disconnection event"""
self.logger.warning(f"Socket.IO disconnected. Attempt {self.retry_count + 1}/{self.max_retries}")
if self.should_reconnect and self.retry_count < self.max_retries:
self._attempt_reconnect()
else:
self.logger.error("Maximum number of reconnection attempts reached")
def _on_error(self, error):
"""Handler for error events"""
self.logger.error(f"Socket.IO error: {str(error)}", exc_info=True)
def _attempt_reconnect(self):
"""Attempt to reconnect with exponential backoff"""
try:
delay = self.retry_delay * (2 ** self.retry_count) # Exponential backoff
self.logger.info(f"Attempting to reconnect in {delay:.2f} seconds...")
time.sleep(delay)
self.connect()
self.retry_count += 1
except Exception as e:
self.logger.error(f"Error during reconnection attempt: {str(e)}", exc_info=True)
if self.retry_count < self.max_retries:
self._attempt_reconnect()
else:
self.logger.error("All reconnection attempts failed")
def _handle_event(self, event, *args):
"""Global handler for all events"""
# Only process registered events
if event in self._handlers:
self.logger.debug(f"Event received in namespace /{self.instance_id}: {event}")
self.logger.debug(f"Event data: {args}")
try:
# Extract event data
raw_data = args[0] if args else {}
# Ensure we're passing the correct object to the callback
if isinstance(raw_data, dict):
self.logger.debug(f"Calling handler for {event} with data: {raw_data}")
self._handlers[event](raw_data)
else:
self.logger.error(f"Invalid data received for event {event}: {raw_data}")
except Exception as e:
self.logger.error(f"Error processing event {event}: {str(e)}", exc_info=True)
def connect(self):
"""Connect to Socket.IO server"""
try:
# Connect only to instance namespace with authentication header
self.sio.connect(
f"{self.base_url}?apikey={self.api_token}",
transports=['websocket'],
namespaces=[f'/{self.instance_id}'],
wait_timeout=30
)
# Join instance-specific room
self.sio.emit('subscribe', {'instance': self.instance_id}, namespace=f'/{self.instance_id}')
except Exception as e:
self.logger.error(f"Error connecting to Socket.IO: {str(e)}", exc_info=True)
raise
def disconnect(self):
"""Disconnect from Socket.IO server"""
self.should_reconnect = False # Prevent reconnection attempts
if self.sio.connected:
self.sio.disconnect()
def on(self, event: str, callback: Callable):
"""
Register a callback for a specific event
Args:
event (str): Event name
callback (Callable): Function to be called when the event occurs
"""
self._handlers[event] = callback

247
env/bin/Activate.ps1 vendored
View File

@ -1,247 +0,0 @@
<#
.Synopsis
Activate a Python virtual environment for the current PowerShell session.
.Description
Pushes the python executable for a virtual environment to the front of the
$Env:PATH environment variable and sets the prompt to signify that you are
in a Python virtual environment. Makes use of the command line switches as
well as the `pyvenv.cfg` file values present in the virtual environment.
.Parameter VenvDir
Path to the directory that contains the virtual environment to activate. The
default value for this is the parent of the directory that the Activate.ps1
script is located within.
.Parameter Prompt
The prompt prefix to display when this virtual environment is activated. By
default, this prompt is the name of the virtual environment folder (VenvDir)
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
.Example
Activate.ps1
Activates the Python virtual environment that contains the Activate.ps1 script.
.Example
Activate.ps1 -Verbose
Activates the Python virtual environment that contains the Activate.ps1 script,
and shows extra information about the activation as it executes.
.Example
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
Activates the Python virtual environment located in the specified location.
.Example
Activate.ps1 -Prompt "MyPython"
Activates the Python virtual environment that contains the Activate.ps1 script,
and prefixes the current prompt with the specified string (surrounded in
parentheses) while the virtual environment is active.
.Notes
On Windows, it may be required to enable this Activate.ps1 script by setting the
execution policy for the user. You can do this by issuing the following PowerShell
command:
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
For more information on Execution Policies:
https://go.microsoft.com/fwlink/?LinkID=135170
#>
Param(
[Parameter(Mandatory = $false)]
[String]
$VenvDir,
[Parameter(Mandatory = $false)]
[String]
$Prompt
)
<# Function declarations --------------------------------------------------- #>
<#
.Synopsis
Remove all shell session elements added by the Activate script, including the
addition of the virtual environment's Python executable from the beginning of
the PATH variable.
.Parameter NonDestructive
If present, do not remove this function from the global namespace for the
session.
#>
function global:deactivate ([switch]$NonDestructive) {
# Revert to original values
# The prior prompt:
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
}
# The prior PYTHONHOME:
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
}
# The prior PATH:
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
}
# Just remove the VIRTUAL_ENV altogether:
if (Test-Path -Path Env:VIRTUAL_ENV) {
Remove-Item -Path env:VIRTUAL_ENV
}
# Just remove VIRTUAL_ENV_PROMPT altogether.
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
}
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
}
# Leave deactivate function in the global namespace if requested:
if (-not $NonDestructive) {
Remove-Item -Path function:deactivate
}
}
<#
.Description
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
given folder, and returns them in a map.
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
two strings separated by `=` (with any amount of whitespace surrounding the =)
then it is considered a `key = value` line. The left hand string is the key,
the right hand is the value.
If the value starts with a `'` or a `"` then the first and last character is
stripped from the value before being captured.
.Parameter ConfigDir
Path to the directory that contains the `pyvenv.cfg` file.
#>
function Get-PyVenvConfig(
[String]
$ConfigDir
) {
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
# An empty map will be returned if no config file is found.
$pyvenvConfig = @{ }
if ($pyvenvConfigPath) {
Write-Verbose "File exists, parse `key = value` lines"
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
$pyvenvConfigContent | ForEach-Object {
$keyval = $PSItem -split "\s*=\s*", 2
if ($keyval[0] -and $keyval[1]) {
$val = $keyval[1]
# Remove extraneous quotations around a string value.
if ("'""".Contains($val.Substring(0, 1))) {
$val = $val.Substring(1, $val.Length - 2)
}
$pyvenvConfig[$keyval[0]] = $val
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
}
}
}
return $pyvenvConfig
}
<# Begin Activate script --------------------------------------------------- #>
# Determine the containing directory of this script
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$VenvExecDir = Get-Item -Path $VenvExecPath
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
# Set values required in priority: CmdLine, ConfigFile, Default
# First, get the location of the virtual environment, it might not be
# VenvExecDir if specified on the command line.
if ($VenvDir) {
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
}
else {
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
Write-Verbose "VenvDir=$VenvDir"
}
# Next, read the `pyvenv.cfg` file to determine any required value such
# as `prompt`.
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
# Next, set the prompt from the command line, or the config file, or
# just use the name of the virtual environment folder.
if ($Prompt) {
Write-Verbose "Prompt specified as argument, using '$Prompt'"
}
else {
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
$Prompt = $pyvenvCfg['prompt'];
}
else {
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
$Prompt = Split-Path -Path $venvDir -Leaf
}
}
Write-Verbose "Prompt = '$Prompt'"
Write-Verbose "VenvDir='$VenvDir'"
# Deactivate any currently active virtual environment, but leave the
# deactivate function in place.
deactivate -nondestructive
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
# that there is an activated venv.
$env:VIRTUAL_ENV = $VenvDir
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
Write-Verbose "Setting prompt to '$Prompt'"
# Set the prompt to include the env name
# Make sure _OLD_VIRTUAL_PROMPT is global
function global:_OLD_VIRTUAL_PROMPT { "" }
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
function global:prompt {
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
_OLD_VIRTUAL_PROMPT
}
$env:VIRTUAL_ENV_PROMPT = $Prompt
}
# Clear PYTHONHOME
if (Test-Path -Path Env:PYTHONHOME) {
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
Remove-Item -Path Env:PYTHONHOME
}
# Add the venv to the PATH
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"

69
env/bin/activate vendored
View File

@ -1,69 +0,0 @@
# This file must be used with "source bin/activate" *from bash*
# you cannot run it directly
deactivate () {
# reset old environment variables
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
PATH="${_OLD_VIRTUAL_PATH:-}"
export PATH
unset _OLD_VIRTUAL_PATH
fi
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
export PYTHONHOME
unset _OLD_VIRTUAL_PYTHONHOME
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r 2> /dev/null
fi
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
PS1="${_OLD_VIRTUAL_PS1:-}"
export PS1
unset _OLD_VIRTUAL_PS1
fi
unset VIRTUAL_ENV
unset VIRTUAL_ENV_PROMPT
if [ ! "${1:-}" = "nondestructive" ] ; then
# Self destruct!
unset -f deactivate
fi
}
# unset irrelevant variables
deactivate nondestructive
VIRTUAL_ENV="/home/davidson/Projects/evolution_client/python/env"
export VIRTUAL_ENV
_OLD_VIRTUAL_PATH="$PATH"
PATH="$VIRTUAL_ENV/bin:$PATH"
export PATH
# unset PYTHONHOME if set
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
# could use `if (set -u; : $PYTHONHOME) ;` in bash
if [ -n "${PYTHONHOME:-}" ] ; then
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
unset PYTHONHOME
fi
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
_OLD_VIRTUAL_PS1="${PS1:-}"
PS1="(env) ${PS1:-}"
export PS1
VIRTUAL_ENV_PROMPT="(env) "
export VIRTUAL_ENV_PROMPT
fi
# This should detect bash and zsh, which have a hash command that must
# be called to get it to forget past commands. Without forgetting
# past commands the $PATH changes we made may not be respected
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
hash -r 2> /dev/null
fi

26
env/bin/activate.csh vendored
View File

@ -1,26 +0,0 @@
# This file must be used with "source bin/activate.csh" *from csh*.
# You cannot run it directly.
# Created by Davide Di Blasi <davidedb@gmail.com>.
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
# Unset irrelevant variables.
deactivate nondestructive
setenv VIRTUAL_ENV "/home/davidson/Projects/evolution_client/python/env"
set _OLD_VIRTUAL_PATH="$PATH"
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
set _OLD_VIRTUAL_PROMPT="$prompt"
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
set prompt = "(env) $prompt"
setenv VIRTUAL_ENV_PROMPT "(env) "
endif
alias pydoc python -m pydoc
rehash

69
env/bin/activate.fish vendored
View File

@ -1,69 +0,0 @@
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
# (https://fishshell.com/); you cannot run it directly.
function deactivate -d "Exit virtual environment and return to normal shell environment"
# reset old environment variables
if test -n "$_OLD_VIRTUAL_PATH"
set -gx PATH $_OLD_VIRTUAL_PATH
set -e _OLD_VIRTUAL_PATH
end
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
set -e _OLD_VIRTUAL_PYTHONHOME
end
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
set -e _OLD_FISH_PROMPT_OVERRIDE
# prevents error when using nested fish instances (Issue #93858)
if functions -q _old_fish_prompt
functions -e fish_prompt
functions -c _old_fish_prompt fish_prompt
functions -e _old_fish_prompt
end
end
set -e VIRTUAL_ENV
set -e VIRTUAL_ENV_PROMPT
if test "$argv[1]" != "nondestructive"
# Self-destruct!
functions -e deactivate
end
end
# Unset irrelevant variables.
deactivate nondestructive
set -gx VIRTUAL_ENV "/home/davidson/Projects/evolution_client/python/env"
set -gx _OLD_VIRTUAL_PATH $PATH
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
# Unset PYTHONHOME if set.
if set -q PYTHONHOME
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
set -e PYTHONHOME
end
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
# fish uses a function instead of an env var to generate the prompt.
# Save the current fish_prompt function as the function _old_fish_prompt.
functions -c fish_prompt _old_fish_prompt
# With the original prompt function renamed, we can override with our own.
function fish_prompt
# Save the return status of the last command.
set -l old_status $status
# Output the venv prompt; color taken from the blue of the Python logo.
printf "%s%s%s" (set_color 4B8BBE) "(env) " (set_color normal)
# Restore the return status of the previous command.
echo "exit $old_status" | .
# Output the original/"old" prompt.
_old_fish_prompt
end
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
set -gx VIRTUAL_ENV_PROMPT "(env) "
end

8
env/bin/docutils vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.__main__ import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
env/bin/keyring vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from keyring.cli import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
env/bin/markdown-it vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from markdown_it.cli.parse import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
env/bin/normalizer vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from charset_normalizer.cli import cli_detect
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(cli_detect())

8
env/bin/pip vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
env/bin/pip3 vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
env/bin/pip3.10 vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from pip._internal.cli.main import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
env/bin/pkginfo vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from pkginfo.commandline import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
env/bin/pygmentize vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from pygments.cmdline import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

1
env/bin/python vendored
View File

@ -1 +0,0 @@
/usr/bin/python

1
env/bin/python3 vendored
View File

@ -1 +0,0 @@
python

1
env/bin/python3.10 vendored
View File

@ -1 +0,0 @@
python

8
env/bin/rst2html vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.core import rst2html
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(rst2html())

8
env/bin/rst2html4 vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.core import rst2html4
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(rst2html4())

8
env/bin/rst2html5 vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.core import rst2html5
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(rst2html5())

8
env/bin/rst2latex vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.core import rst2latex
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(rst2latex())

8
env/bin/rst2man vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.core import rst2man
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(rst2man())

8
env/bin/rst2odt vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.core import rst2odt
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(rst2odt())

View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.core import rst2pseudoxml
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(rst2pseudoxml())

8
env/bin/rst2s5 vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.core import rst2s5
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(rst2s5())

8
env/bin/rst2xetex vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.core import rst2xetex
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(rst2xetex())

8
env/bin/rst2xml vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from docutils.core import rst2xml
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(rst2xml())

8
env/bin/twine vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from twine.__main__ import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
env/bin/wheel vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from wheel.cli import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

8
env/bin/wsdump vendored
View File

@ -1,8 +0,0 @@
#!/home/davidson/Projects/evolution_client/python/env/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from websocket._wsdump import main
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(main())

View File

@ -1,25 +0,0 @@
Copyright 2012-2018 Dmitry Shachnev <mitya57@gmail.com>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,114 +0,0 @@
Metadata-Version: 2.1
Name: SecretStorage
Version: 3.3.3
Summary: Python bindings to FreeDesktop.org Secret Service API
Home-page: https://github.com/mitya57/secretstorage
Author: Dmitry Shachnev
Author-email: mitya57@gmail.com
License: BSD 3-Clause License
Platform: Linux
Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.6
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: cryptography (>=2.0)
Requires-Dist: jeepney (>=0.6)
.. image:: https://github.com/mitya57/secretstorage/workflows/tests/badge.svg
:target: https://github.com/mitya57/secretstorage/actions
:alt: GitHub Actions status
.. image:: https://codecov.io/gh/mitya57/secretstorage/branch/master/graph/badge.svg
:target: https://codecov.io/gh/mitya57/secretstorage
:alt: Coverage status
.. image:: https://readthedocs.org/projects/secretstorage/badge/?version=latest
:target: https://secretstorage.readthedocs.io/en/latest/
:alt: ReadTheDocs status
Module description
==================
This module provides a way for securely storing passwords and other secrets.
It uses D-Bus `Secret Service`_ API that is supported by GNOME Keyring,
KWallet (since version 5.97) and KeePassXC.
The main classes provided are ``secretstorage.Item``, representing a secret
item (that has a *label*, a *secret* and some *attributes*) and
``secretstorage.Collection``, a place items are stored in.
SecretStorage supports most of the functions provided by Secret Service,
including creating and deleting items and collections, editing items,
locking and unlocking collections (asynchronous unlocking is also supported).
The documentation can be found on `secretstorage.readthedocs.io`_.
.. _`Secret Service`: https://specifications.freedesktop.org/secret-service/
.. _`secretstorage.readthedocs.io`: https://secretstorage.readthedocs.io/en/latest/
Building the module
===================
.. note::
SecretStorage 3.x supports Python 3.6 and newer versions.
If you have an older version of Python, install SecretStorage 2.x::
pip install "SecretStorage < 3"
SecretStorage requires these packages to work:
* Jeepney_
* `python-cryptography`_
To build SecretStorage, use this command::
python3 setup.py build
If you have Sphinx_ installed, you can also build the documentation::
python3 setup.py build_sphinx
.. _Jeepney: https://pypi.org/project/jeepney/
.. _`python-cryptography`: https://pypi.org/project/cryptography/
.. _Sphinx: http://sphinx-doc.org/
Testing the module
==================
First, make sure that you have the Secret Service daemon installed.
The `GNOME Keyring`_ is the reference server-side implementation for the
Secret Service specification.
.. _`GNOME Keyring`: https://download.gnome.org/sources/gnome-keyring/
Then, start the daemon and unlock the ``default`` collection, if needed.
The testsuite will fail to run if the ``default`` collection exists and is
locked. If it does not exist, the testsuite can also use the temporary
``session`` collection, as provided by the GNOME Keyring.
Then, run the Python unittest module::
python3 -m unittest discover -s tests
If you want to run the tests in an isolated or headless environment, run
this command in a D-Bus session::
dbus-run-session -- python3 -m unittest discover -s tests
Get the code
============
SecretStorage is available under BSD license. The source code can be found
on GitHub_.
.. _GitHub: https://github.com/mitya57/secretstorage

View File

@ -1,21 +0,0 @@
SecretStorage-3.3.3.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
SecretStorage-3.3.3.dist-info/LICENSE,sha256=cPa_yndjPDXvohgyjtpUhtcFTCkU1hggmA43h5dSCiU,1504
SecretStorage-3.3.3.dist-info/METADATA,sha256=ZScD5voEgjme04wlw9OZigESMxLa2xG_eaIeZ_IMqJI,4027
SecretStorage-3.3.3.dist-info/RECORD,,
SecretStorage-3.3.3.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92
SecretStorage-3.3.3.dist-info/top_level.txt,sha256=hveSi1OWGaEt3kEVbjmZ0M-ASPxi6y-nTPVa-d3c0B4,14
secretstorage/__init__.py,sha256=W1p-HB1Qh12Dv6K8ml0Wj_MzN09_dEeezJjQZAHf-O4,3364
secretstorage/__pycache__/__init__.cpython-310.pyc,,
secretstorage/__pycache__/collection.cpython-310.pyc,,
secretstorage/__pycache__/defines.cpython-310.pyc,,
secretstorage/__pycache__/dhcrypto.cpython-310.pyc,,
secretstorage/__pycache__/exceptions.cpython-310.pyc,,
secretstorage/__pycache__/item.cpython-310.pyc,,
secretstorage/__pycache__/util.cpython-310.pyc,,
secretstorage/collection.py,sha256=lHwSOkFO5sRspgcUBoBI8ZG2au2bcUSO6X64ksVdnsQ,9436
secretstorage/defines.py,sha256=DzUrEWzSvBlN8kK2nVXnLGlCZv7HWNyfN1AYqRmjKGE,807
secretstorage/dhcrypto.py,sha256=BiuDoNvNmd8i7Ul4ENouRnbqFE3SUmTUSAn6RVvn7Tg,2578
secretstorage/exceptions.py,sha256=1uUZXTua4jRZf4PKDIT2SVWcSKP2lP97s8r3eJZudio,1655
secretstorage/item.py,sha256=3niFSjOzwrB2hV1jrg78RXgBsTrpw44852VpZHXUpeE,5813
secretstorage/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
secretstorage/util.py,sha256=vHu01QaooMQ5sRdRDFX9pg7rrzfJWF9vg0plm3Zg0Po,6755

View File

@ -1,5 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.37.1)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -1 +0,0 @@
secretstorage

View File

@ -1,132 +0,0 @@
import sys
import os
import re
import importlib
import warnings
is_pypy = '__pypy__' in sys.builtin_module_names
warnings.filterwarnings('ignore',
r'.+ distutils\b.+ deprecated',
DeprecationWarning)
def warn_distutils_present():
if 'distutils' not in sys.modules:
return
if is_pypy and sys.version_info < (3, 7):
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
return
warnings.warn(
"Distutils was imported before Setuptools, but importing Setuptools "
"also replaces the `distutils` module in `sys.modules`. This may lead "
"to undesirable behaviors or errors. To avoid these issues, avoid "
"using distutils directly, ensure that setuptools is installed in the "
"traditional way (e.g. not an editable install), and/or make sure "
"that setuptools is always imported before distutils.")
def clear_distutils():
if 'distutils' not in sys.modules:
return
warnings.warn("Setuptools is replacing distutils.")
mods = [name for name in sys.modules if re.match(r'distutils\b', name)]
for name in mods:
del sys.modules[name]
def enabled():
"""
Allow selection of distutils by environment variable.
"""
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib')
return which == 'local'
def ensure_local_distutils():
clear_distutils()
# With the DistutilsMetaFinder in place,
# perform an import to cause distutils to be
# loaded from setuptools._distutils. Ref #2906.
add_shim()
importlib.import_module('distutils')
remove_shim()
# check that submodules load as expected
core = importlib.import_module('distutils.core')
assert '_distutils' in core.__file__, core.__file__
def do_override():
"""
Ensure that the local copy of distutils is preferred over stdlib.
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
for more motivation.
"""
if enabled():
warn_distutils_present()
ensure_local_distutils()
class DistutilsMetaFinder:
def find_spec(self, fullname, path, target=None):
if path is not None:
return
method_name = 'spec_for_{fullname}'.format(**locals())
method = getattr(self, method_name, lambda: None)
return method()
def spec_for_distutils(self):
import importlib.abc
import importlib.util
class DistutilsLoader(importlib.abc.Loader):
def create_module(self, spec):
return importlib.import_module('setuptools._distutils')
def exec_module(self, module):
pass
return importlib.util.spec_from_loader('distutils', DistutilsLoader())
def spec_for_pip(self):
"""
Ensure stdlib distutils when running under pip.
See pypa/pip#8761 for rationale.
"""
if self.pip_imported_during_build():
return
clear_distutils()
self.spec_for_distutils = lambda: None
@staticmethod
def pip_imported_during_build():
"""
Detect if pip is being imported in a build script. Ref #2355.
"""
import traceback
return any(
frame.f_globals['__file__'].endswith('setup.py')
for frame, line in traceback.walk_stack(None)
)
DISTUTILS_FINDER = DistutilsMetaFinder()
def add_shim():
sys.meta_path.insert(0, DISTUTILS_FINDER)
def remove_shim():
try:
sys.meta_path.remove(DISTUTILS_FINDER)
except ValueError:
pass

View File

@ -1 +0,0 @@
__import__('_distutils_hack').do_override()

View File

@ -1,17 +0,0 @@
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.

View File

@ -1,46 +0,0 @@
Metadata-Version: 2.1
Name: backports.tarfile
Version: 1.2.0
Summary: Backport of CPython tarfile module
Author-email: "Jason R. Coombs" <jaraco@jaraco.com>
Project-URL: Homepage, https://github.com/jaraco/backports.tarfile
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.8
Description-Content-Type: text/x-rst
License-File: LICENSE
Provides-Extra: docs
Requires-Dist: sphinx >=3.5 ; extra == 'docs'
Requires-Dist: jaraco.packaging >=9.3 ; extra == 'docs'
Requires-Dist: rst.linker >=1.9 ; extra == 'docs'
Requires-Dist: furo ; extra == 'docs'
Requires-Dist: sphinx-lint ; extra == 'docs'
Provides-Extra: testing
Requires-Dist: pytest !=8.1.*,>=6 ; extra == 'testing'
Requires-Dist: pytest-checkdocs >=2.4 ; extra == 'testing'
Requires-Dist: pytest-cov ; extra == 'testing'
Requires-Dist: pytest-enabler >=2.2 ; extra == 'testing'
Requires-Dist: jaraco.test ; extra == 'testing'
Requires-Dist: pytest !=8.0.* ; extra == 'testing'
.. image:: https://img.shields.io/pypi/v/backports.tarfile.svg
:target: https://pypi.org/project/backports.tarfile
.. image:: https://img.shields.io/pypi/pyversions/backports.tarfile.svg
.. image:: https://github.com/jaraco/backports.tarfile/actions/workflows/main.yml/badge.svg
:target: https://github.com/jaraco/backports.tarfile/actions?query=workflow%3A%22tests%22
:alt: tests
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json
:target: https://github.com/astral-sh/ruff
:alt: Ruff
.. .. image:: https://readthedocs.org/projects/backportstarfile/badge/?version=latest
.. :target: https://backportstarfile.readthedocs.io/en/latest/?badge=latest
.. image:: https://img.shields.io/badge/skeleton-2024-informational
:target: https://blog.jaraco.com/skeleton

View File

@ -1,16 +0,0 @@
backports.tarfile-1.2.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
backports.tarfile-1.2.0.dist-info/LICENSE,sha256=htoPAa6uRjSKPD1GUZXcHOzN55956HdppkuNoEsqR0E,1023
backports.tarfile-1.2.0.dist-info/METADATA,sha256=ghXFTq132dxaEIolxr3HK1mZqm9iyUmaRANZQSr6WlE,2020
backports.tarfile-1.2.0.dist-info/RECORD,,
backports.tarfile-1.2.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
backports.tarfile-1.2.0.dist-info/top_level.txt,sha256=cGjaLMOoBR1FK0ApojtzWVmViTtJ7JGIK_HwXiEsvtU,10
backports/__init__.py,sha256=iOEMwnlORWezdO8-2vxBIPSR37D7JGjluZ8f55vzxls,81
backports/__pycache__/__init__.cpython-310.pyc,,
backports/tarfile/__init__.py,sha256=Pwf2qUIfB0SolJPCKcx3vz3UEu_aids4g4sAfxy94qg,108491
backports/tarfile/__main__.py,sha256=Yw2oGT1afrz2eBskzdPYL8ReB_3liApmhFkN2EbDmc4,59
backports/tarfile/__pycache__/__init__.cpython-310.pyc,,
backports/tarfile/__pycache__/__main__.cpython-310.pyc,,
backports/tarfile/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
backports/tarfile/compat/__pycache__/__init__.cpython-310.pyc,,
backports/tarfile/compat/__pycache__/py38.cpython-310.pyc,,
backports/tarfile/compat/py38.py,sha256=iYkyt_gvWjLzGUTJD9TuTfMMjOk-ersXZmRlvQYN2qE,568

View File

@ -1,5 +0,0 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.43.0)
Root-Is-Purelib: true
Tag: py3-none-any

View File

@ -1 +0,0 @@
__path__ = __import__('pkgutil').extend_path(__path__, __name__) # type: ignore

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
from . import main
if __name__ == '__main__':
main()

Some files were not shown because too many files have changed in this diff Show More