Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ALPHA: Next RELEASE #84

Merged
merged 8 commits into from
Oct 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,27 @@ assignees: ''
---

**Describe the bug**
A clear and concise description of what the bug is.

The current implementation has a bug that causes unexpected behavior under specific circumstances. This issue needs to be addressed to ensure the application functions reliably in all scenarios.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
1. Navigate to the '...' section.
2. Click on '....' button.
3. Scroll down to the '....' subsection.
4. Observe the error message displayed.

**Expected behavior**
A clear and concise description of what you expected to happen.
I expected that the application would [describe the expected behavior in detail].

**Screenshots**
If applicable, add screenshots to help explain your problem.
[Attach relevant screenshots, if available, to provide visual context for the issue.]

**Versions (please complete the following information):**
- Novu version
- Python version
- Novu-python version
- Novu version: [Specify the version number, e.g., 1.2.3]
- Python version: [Provide the version of Python being used, e.g., 3.7.9]
- Novu-python version: [Specify the version of the Novu-python library, if applicable]

**Additional context**
Add any other context about the problem here.
[Provide any further information or context about the problem. This may include any specific conditions or inputs that trigger the bug, any error messages received, or any related background information.]

**Note to Reviewers:**
Please thoroughly investigate the steps to reproduce and verify if the expected behavior aligns with the reported issue. Additionally, consider the impact of this bug on the overall user experience.
7 changes: 3 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,18 @@ repos:
additional_dependencies: ["bandit[toml]"]

- repo: https://github.com/codespell-project/codespell
rev: "v2.1.0"
rev: "v2.2.5"
hooks:
- id: codespell
args: ["-w"]
additional_dependencies:
- tomli
additional_dependencies: ["tomli"]

- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
rev: v9.0.0
hooks:
- id: commitlint
stages: [commit-msg]
additional_dependencies: ['@commitlint/config-angular']
additional_dependencies: ["@commitlint/config-angular"]

- repo: https://github.com/asottile/pyupgrade
rev: v3.3.1
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

### Features

* **#9:** add notifications ressource in wrappers ([f33a32f](https://github.com/novuhq/novu-python/commit/f33a32fc788085516515ba7a3fa54181248e43bf)), closes [#9](https://github.com/novuhq/novu-python/issues/9)
* **#9:** add notifications resource in wrappers ([f33a32f](https://github.com/novuhq/novu-python/commit/f33a32fc788085516515ba7a3fa54181248e43bf)), closes [#9](https://github.com/novuhq/novu-python/issues/9)
* **tenant:** introduce Tenant API ([330cb19](https://github.com/novuhq/novu-python/commit/330cb19a679f1284487acd8f6beabd3edc681448))

## [1.3.1](https://github.com/novuhq/novu-python/compare/v1.3.0...v1.3.1) (2023-08-30)
Expand Down
68 changes: 67 additions & 1 deletion novu/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,82 @@
import logging
import os
from json.decoder import JSONDecodeError
from typing import Optional
from typing import Generic, List, Optional, Type, TypeVar

import requests

from novu.config import NovuConfig
from novu.dto.base import CamelCaseDto
from novu.helpers import SentryProxy

LOGGER = logging.getLogger(__name__)


_C_co = TypeVar("_C_co", bound=CamelCaseDto, covariant=True)


class PaginationIterator(Generic[_C_co]): # pylint: disable=R0902
"""The class is a generic iterator which allow to iterate directly on result without
looking for pagination during handling.

Args:
api: The api used to call
item_class: The Dto to parse each items return by API.
url: The URL to query during fetch.
payload: Payload to query during fetch.
"""

def __init__(self, api: "Api", item_class: Type[_C_co], url: str, payload: Optional[dict] = None):
self.__item_class = item_class
self.__api = api
self.__url = url
self.__payload = payload or {}

self.__has_more = False
self.__page = 0
self.__limit = 10
self.__index = 0
self.__buffer: List[_C_co] = []

self.__payload["limit"] = self.__limit
self.__payload["page"] = self.__page
self.__fetch_data()

def __iter__(self) -> "PaginationIterator[_C_co]":
return self

def __next__(self) -> _C_co:
return self.next()

def next(self) -> _C_co:
"""Implementation of the next behavior for the iterator."""
if self.__index < len(self.__buffer):
result, self.__index = self.__buffer[self.__index], self.__index + 1
return result

if self.__has_more:
self.__fetch_data()

result, self.__index = self.__buffer[self.__index], self.__index + 1
return result

raise StopIteration()

def __fetch_data(self):
self.__payload["page"] = self.__page
self.__page += 1

data = self.__api.handle_request(
method="GET",
url=self.__url,
payload=self.__payload,
)

self.__has_more = data.get("hasMore", data.get("totalCount", 0) > (self.__page + 1 * self.__limit))
self.__buffer = list(map(self.__item_class.from_camel_case, data.get("data", [])))
self.__index = 0


class Api: # pylint: disable=R0903
"""Base class for all API in the Novu client"""

Expand Down
7 changes: 5 additions & 2 deletions novu/api/change.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,20 @@ def __init__(

self._change_url = f"{self._url}{CHANGES_ENDPOINT}"

def list(self, page: Optional[int] = None, limit: Optional[int] = None) -> PaginatedChangeDto:
def list(
self, page: Optional[int] = None, limit: Optional[int] = None, promoted: str = "false"
) -> PaginatedChangeDto:
"""List existing changes

Args:
page: Page to retrieve. Defaults to None.
limit: Size of the page to retrieve. Defaults to None.
promoted: Required string to retrieve changes.

Returns:
Paginated list of change
"""
payload: Dict[str, Union[int, str]] = {}
payload: Dict[str, Union[int, str]] = {"promoted": promoted}
if page:
payload["page"] = page
if limit:
Expand Down
25 changes: 23 additions & 2 deletions novu/api/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

import requests

from novu.api.base import Api
from novu.api.base import Api, PaginationIterator
from novu.constants import MESSAGES_ENDPOINT
from novu.dto.message import PaginatedMessageDto
from novu.dto.message import MessageDto, PaginatedMessageDto


class MessageApi(Api):
Expand Down Expand Up @@ -47,6 +47,27 @@ def list(

return PaginatedMessageDto.from_camel_case(self.handle_request("GET", self._message_url, payload=payload))

def stream(
self, channel: Optional[str] = None, subscriber_id: Optional[str] = None
) -> PaginationIterator[MessageDto]:
"""Stream all existing messages into an iterator.

Args:
channel: The channel for the messages you wish to list. Defaults to None.
subscriber_id: The subscriberId for the subscriber you like to list messages for

Returns:
An iterator on all messages available.
"""
payload = {}

if channel:
payload["channel"] = channel
if subscriber_id:
payload["subscriberId"] = subscriber_id

return PaginationIterator(self, MessageDto, self._message_url, payload=payload)

def delete(self, message_id: str) -> bool:
"""Deletes a message entity from the Novu platform

Expand Down
60 changes: 51 additions & 9 deletions novu/api/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@

import requests

from novu.api.base import Api
from novu.api.base import Api, PaginationIterator
from novu.constants import NOTIFICATION_ENDPOINT
from novu.dto.notification import ActivityGraphStatesDto, ActivityNotificationDto
from novu.dto.notification import (
ActivityGraphStatesDto,
ActivityNotificationDto,
PaginatedActivityNotificationDto,
)


class NotificationApi(Api):
Expand All @@ -26,13 +30,13 @@ def __init__(

def list(
self,
channels: List[str],
templates: List[str],
emails: List[str],
search: str,
channels: Optional[List[str]] = None,
templates: Optional[List[str]] = None,
emails: Optional[List[str]] = None,
search: Optional[str] = None,
page: Optional[int] = 0,
transaction_id: Optional[str] = None,
) -> ActivityNotificationDto:
) -> PaginatedActivityNotificationDto:
"""Trigger an event to get all notifications.

Args:
Expand Down Expand Up @@ -66,10 +70,48 @@ def list(
"page": page,
"transactionId": transaction_id,
}
return ActivityNotificationDto.from_camel_case(
self.handle_request("GET", f"{self._notification_url}", payload=payload)["data"]
return PaginatedActivityNotificationDto.from_camel_case(
self.handle_request("GET", f"{self._notification_url}", payload=payload)
)

def stream(
self,
channels: Optional[List[str]] = None,
templates: Optional[List[str]] = None,
emails: Optional[List[str]] = None,
search: Optional[str] = None,
transaction_id: Optional[str] = None,
) -> PaginationIterator[ActivityNotificationDto]:
"""Stream all existing notifications into an iterator.

Args:
channels: A required parameter, should be an array of strings representing
available notification channels, such as "in_app", "email", "sms",
"chat", and "push".

templates: A required parameter, should be an array of strings representing
the notification templates.

emails: A required parameter, should be an array of strings representing
the email addresses associated with the notification.

search: A required parameter, should be a string representing the search query.

transaction_id: A required parameter, should be a string representing the
transaction ID associated with the notification.

Returns:
An iterator on all notifications available.
"""
payload = {
"channels": channels,
"templates": templates,
"emails": emails,
"search": search,
"transactionId": transaction_id,
}
return PaginationIterator(self, ActivityNotificationDto, self._notification_url, payload=payload)

def stats(self) -> Tuple[int, int]:
"""Gets notifications stats

Expand Down
38 changes: 38 additions & 0 deletions novu/api/notification_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,41 @@ def create(self, name: str) -> NotificationGroupDto:
return NotificationGroupDto.from_camel_case(
self.handle_request("POST", self._notification_group_url, {"name": name})["data"]
)

def get(self, _id: str) -> NotificationGroupDto:
"""Method to get a notification group

Args:
_id: The id of the notification group.

Returns:
The notification group.
"""
return NotificationGroupDto.from_camel_case(
self.handle_request("GET", f"{self._notification_group_url}/{_id}")["data"]
)

def patch(self, _id: str, name: str) -> NotificationGroupDto:
"""Method to patch a notification group

Args:
_id: The id of the notification group.
name: The name of the notification group.

Returns:
The patched notification group.
"""
return NotificationGroupDto.from_camel_case(
self.handle_request("PATCH", f"{self._notification_group_url}/{_id}", {"name": name})["data"]
)

def delete(self, _id: str) -> NotificationGroupDto:
"""Method to delete a notification group

Args:
_id: The id of the notification group.

Returns:
The deleted notification group.
"""
return self.handle_request("DELETE", f"{self._notification_group_url}/{_id}")["data"]
10 changes: 9 additions & 1 deletion novu/api/notification_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import requests

from novu.api.base import Api
from novu.api.base import Api, PaginationIterator
from novu.constants import NOTIFICATION_TEMPLATES_ENDPOINT
from novu.dto.notification_template import (
NotificationTemplateDto,
Expand Down Expand Up @@ -45,6 +45,14 @@ def list(self, page: Optional[int] = None, limit: Optional[int] = None) -> Pagin
self.handle_request("GET", self._notification_template_url, payload=payload)
)

def stream(self) -> PaginationIterator[NotificationTemplateDto]:
"""Stream all existing workflows into an iterator.

Returns:
An iterator on all workflows available.
"""
return PaginationIterator(self, NotificationTemplateDto, self._notification_template_url)

def create(self, notification_template: NotificationTemplateFormDto) -> NotificationTemplateDto:
"""Create a template using the notification template form definition

Expand Down
10 changes: 9 additions & 1 deletion novu/api/subscriber.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import requests

from novu.api.base import Api
from novu.api.base import Api, PaginationIterator
from novu.constants import SUBSCRIBERS_ENDPOINT
from novu.dto.subscriber import (
PaginatedSubscriberDto,
Expand Down Expand Up @@ -44,6 +44,14 @@ def list(self, page: Optional[int] = None) -> PaginatedSubscriberDto:

return PaginatedSubscriberDto.from_camel_case(self.handle_request("GET", self._subscriber_url, payload=payload))

def stream(self) -> PaginationIterator[SubscriberDto]:
"""Stream all existing subscribers into an iterator.

Returns:
An iterator on all subscribers available.
"""
return PaginationIterator(self, SubscriberDto, self._subscriber_url)

def create(self, subscriber: SubscriberDto) -> SubscriberDto:
"""Method to push a given subscriber instance to Novu

Expand Down
Loading
Loading