Skip to content

Commit

Permalink
Improve project info
Browse files Browse the repository at this point in the history
  • Loading branch information
pdeziel committed Oct 16, 2023
1 parent f6bdb53 commit e76fc78
Show file tree
Hide file tree
Showing 9 changed files with 438 additions and 62 deletions.
45 changes: 20 additions & 25 deletions pyensign/ensign.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,27 @@
import json
import inspect
from datetime import timedelta
from typing import (
List,
Optional,
Union,
AsyncGenerator,
Dict,
Any,
Coroutine,
Callable,
)

from ulid import ULID
from pyensign.topics import Topic
from pyensign.projects import Project
from pyensign.connection import Client
from pyensign.events import from_object
from pyensign.status import ServerStatus
from pyensign.api.v1beta1.query import format_query
from pyensign.utils.topics import Topic, TopicCache
from pyensign.utils.topics import TopicCache
from pyensign.connection import Connection
from pyensign.api.v1beta1 import topic_pb2, query_pb2
from pyensign.api.v1beta1 import topic_pb2
from pyensign.auth.client import AuthClient
from pyensign.enum import (
TopicState,
Expand All @@ -26,22 +39,6 @@
EnsignTopicNotFoundError,
)

from typing import (
List,
Tuple,
Optional,
Union,
AsyncGenerator,
Dict,
Any,
Iterable,
Coroutine,
Type,
Callable,
)

from ulid import ULID


class Ensign:
"""
Expand Down Expand Up @@ -591,11 +588,9 @@ async def set_topic_sharding_strategy(
state = await self.client.set_topic_sharding_strategy(id, strategy)
return TopicState.convert(state.state)

async def info(
self, topic_ids: List[str] = []
) -> Any: # Placeholder for return type
async def info(self, topic_ids: List[str] = []) -> Any:
"""
Get aggregated statistics for topics in the project.
Get information about the project and topics in the project.
Parameters
----------
Expand All @@ -605,8 +600,8 @@ async def info(
Returns
-------
api.v1beta1.ProjectInfo
The aggregated statistics for the topics in the project.
projects.Project
The project info.
"""

if not isinstance(topic_ids, list):
Expand All @@ -620,7 +615,7 @@ async def info(
except ValueError:
raise ValueError(f"not parseable as a topic ID: {id}")

return await self.client.info(topics)
return Project.from_info(await self.client.info(topics))

async def status(self) -> ServerStatus:
"""
Expand Down
11 changes: 11 additions & 0 deletions pyensign/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,17 @@ def __repr__(self):
def __str__(self):
return "{} v{}".format(self.name, self.semver())

@classmethod
def convert(cls, pb_val):
"""
Convert a protocol buffer Type from its protocol buffer representation.
"""
type = cls(pb_val.name)
type.major_version = pb_val.major_version
type.minor_version = pb_val.minor_version
type.patch_version = pb_val.patch_version
return type


@total_ordering
class EventState(Enum):
Expand Down
50 changes: 50 additions & 0 deletions pyensign/projects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from pyensign.utils import ulid
from pyensign.topics import Topic


class Project(object):
"""
A project is a collection of topics. Similar to a traditional database, if you have
access to the project, you have access to all of its topics.
"""

def __init__(self, id):
self.id = ulid.parse(id)
self.num_topics = 0
self.num_readonly_topics = 0
self.events = 0
self.duplicates = 0
self.data_size_bytes = 0
self.topics = []

def __repr__(self):
return "Project(id={})".format(self.id)

def __str__(self):
s = "Project"
s += "\n\tid={}".format(self.id)
s += "\n\tnum_topics={}".format(self.num_topics)
s += "\n\tnum_readonly_topics={}".format(self.num_readonly_topics)
s += "\n\tevents={}".format(self.events)
s += "\n\tduplicates={}".format(self.duplicates)
s += "\n\tdata_size_bytes={}".format(self.data_size_bytes)
for topic in self.topics:
s += "\n\t"
s += str(topic).replace("\t", "\t\t")
return s

@classmethod
def from_info(cls, pb_val):
"""
Convert a protocol buffer ProjectInfo into a Project.
"""

project = cls(pb_val.project_id)
project.num_topics = pb_val.num_topics
project.num_readonly_topics = pb_val.num_readonly_topics
project.events = pb_val.events
project.duplicates = pb_val.duplicates
project.data_size_bytes = pb_val.data_size_bytes
for topic in pb_val.topics:
project.topics.append(Topic.from_info(topic))
return project
121 changes: 121 additions & 0 deletions pyensign/topics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from pyensign.utils import ulid
from pyensign.events import Type


class Topic(object):
"""
A Topic is a stream of ordered events. Topics have a human readable name but also
have a unique ID.
"""

def __init__(self, id=None, name="", topic_str=""):
if id:
self.id = ulid.parse(id)
else:
self.id = None
self.name = name
self.topic_str = topic_str
self.project_id = None
self.event_offset_id = None
self.events = 0
self.duplicates = 0
self.data_size_bytes = 0
self.types = []

def __hash__(self):
if self.id is None:
raise ValueError("cannot hash topic with no ID")
return hash(str(self.id))

def __eq__(self, other):
return self.id == other.id

def __repr__(self):
repr = "Topic("
if self.id:
repr += "id={}, ".format(self.id)
if self.name:
repr += "name={}, ".format(self.name)
if self.topic_str:
repr += "topic_str={}".format(self.topic_str)
repr += ")"
return repr

def __str__(self):
s = "Topic"
if self.id:
s += "\n\tid={}".format(self.id)
if self.name:
s += "\n\tname={}".format(self.name)
if self.project_id:
s += "\n\tproject_id={}".format(self.project_id)
if self.event_offset_id:
s += "\n\tevent_offset_id={}".format(self.event_offset_id)
if self.topic_str:
s += "\n\ttopic_str={}".format(self.topic_str)
s += "\n\tevents={}".format(self.events)
s += "\n\tduplicates={}".format(self.duplicates)
s += "\n\tdata_size_bytes={}".format(self.data_size_bytes)
s += "\n\ttypes:{}".format(len(self.types))
for type in self.types:
s += "\n\t"
s += str(type).replace("\t", "\t\t")
return s

@classmethod
def from_info(cls, pb_val):
"""
Convert a protocol buffer TopicInfo into a Topic.
"""

topic = cls(id=pb_val.topic_id)
topic.project_id = ulid.parse(pb_val.project_id)
topic.event_offset_id = pb_val.event_offset_id
topic.events = pb_val.events
topic.duplicates = pb_val.duplicates
topic.data_size_bytes = pb_val.data_size_bytes
for type in pb_val.types:
topic.types.append(EventType.from_info(type))
return topic


class EventType(object):
"""
An EventType represents a type of event that was published to a topic, which
includes the schema type and the MIME type.
"""

def __init__(self, type, mimetype):
self.type = type
self.mimetype = mimetype
self.events = 0
self.duplicates = 0
self.data_size_bytes = 0

def __repr__(self):
repr = "EventType("
repr += "type={}".format(self.type)
repr += ", mimetype={}".format(self.mimetype)
repr += ")"
return repr

def __str__(self):
s = "EventType"
s += "\n\ttype={}".format(self.type)
s += "\n\tmimetype={}".format(self.mimetype)
s += "\n\tevents={}".format(self.events)
s += "\n\tduplicates={}".format(self.duplicates)
s += "\n\tdata_size_bytes={}".format(self.data_size_bytes)
return s

@classmethod
def from_info(cls, pb_val):
"""
Convert a protocol buffer EventType into an EventType.
"""

type = cls(Type.convert(pb_val.type), pb_val.mimetype)
type.events = pb_val.events
type.duplicates = pb_val.duplicates
type.data_size_bytes = pb_val.data_size_bytes
return type
20 changes: 0 additions & 20 deletions pyensign/utils/topics.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,6 @@
from pyensign.exceptions import CacheMissError


class Topic:
"""
Topics have a user-defined name but are also unique by ULID. This class stores both
representations to make topics easier to work with.
"""

def __init__(self, id=None, name="", topic_str=""):
self.id = id
self.name = name
self.topic_str = topic_str

def __hash__(self):
if self.id is None:
raise ValueError("cannot hash topic with no ID")
return hash(str(self.id))

def __eq__(self, other):
return self.id == other.id


class TopicCache(Cache):
"""
TopicCache extends the functionality of the Cache class to support topic ID parsing.
Expand Down
16 changes: 16 additions & 0 deletions pyensign/utils/ulid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from ulid import ULID


def parse(id):
"""
Parse a ULID from its string or bytes representation.
"""

if isinstance(id, ULID):
return id
elif isinstance(id, str):
return ULID.from_str(id)
elif isinstance(id, bytes):
return ULID.from_bytes(id)
else:
raise TypeError("cannot parse ULID from {}".format(type(id)))
Loading

0 comments on commit e76fc78

Please sign in to comment.