Repository: OpenMined/PySyft
Pull request: Adding range support to PyPrimitive
## Description
fixes #5391 
Adds support for `range` object as `PyPrimitive`

## Affected Dependencies
None

## How has this been tested?
Added serde tests and integrated cpython range tests

## Checklist
- [x] I have followed the [Contribution Guidelines](https://github.com/OpenMined/.github/blob/master/CONTRIBUTING.md) and [Code of Conduct](https://github.com/OpenMined/.github/blob/master/CODE_OF_CONDUCT.md)
- [x] I have commented my code following the [OpenMined Styleguide](https://github.com/OpenMined/.github/blob/master/STYLEGUIDE.md)
- [x] I have labeled this PR with the relevant [Type labels](https://github.com/OpenMined/.github/labels?q=Type%3A)
- [x] My changes are covered by tests


Problem statement:
Add support for ranges
## Description
We don't have support for `range` objects. We would like to add ranges in our primitive supported types.

Definition of DONE:
- [ ] Add some serde tests
- [ ] Make sure Ranges works on other types like Tensor or Pandas
- [ ] Add to `primitive_factory`

## Additional Context
We would like to have wrappers for range objects, to be able to manipulate them.

Hints from issue discussion:
@tudorcebere I will create a WIP PR, would like to have in-progress feedback.

Commit message: Created range proto and boiler plate in range.py

Patch to review:
diff --git a/proto/lib/python/range.proto b/proto/lib/python/range.proto
new file mode 100644
index 00000000000..715eeaf2e5c
--- /dev/null
+++ b/proto/lib/python/range.proto
@@ -0,0 +1,14 @@
+syntax = "proto3";
+
+package syft.lib.python;
+import "proto/core/common/common_object.proto";
+
+message Range {
+  int64 start = 1;
+  int64 stop = 2;
+  int64 step = 3;
+  bool has_start = 4;
+  bool has_stop = 5;
+  bool has_step = 6;
+  syft.core.common.UID id = 7;
+}
diff --git a/src/syft/lib/python/range.py b/src/syft/lib/python/range.py
new file mode 100644
index 00000000000..550fa8ebba0
--- /dev/null
+++ b/src/syft/lib/python/range.py
@@ -0,0 +1,36 @@
+# stdlib
+from typing import Any
+from typing import Optional
+from typing import Union
+
+# syft relative
+from ...core.common import UID
+from ...proto.lib.python.range_pb2 import Range as Range_PB
+from .primitive_factory import PyPrimitive
+
+
+class Range(PyPrimitive):
+    def __init__(
+        self,
+        start: Any = None,
+        stop: Union[Any] = None,
+        step: Union[Any] = None,
+        id: Optional[UID] = None,
+    ):
+        if stop is None and step is None:
+            stop = start
+            start = None
+
+        self.value = range(start, stop, step)
+        self._id = id if id else UID()
+
+    @property
+    def id(self) -> UID:
+        return self._id
+
+    def _object2proto(self) -> Range_PB:
+        pass
+
+    @staticmethod
+    def _proto2object(proto: Range_PB) -> "Range":
+        pass
diff --git a/src/syft/proto/lib/python/range_pb2.py b/src/syft/proto/lib/python/range_pb2.py
new file mode 100644
index 00000000000..73f1ed2c289
--- /dev/null
+++ b/src/syft/proto/lib/python/range_pb2.py
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: proto/lib/python/range.proto
+"""Generated protocol buffer code."""
+# third party
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+# syft absolute
+from syft.proto.core.common import (
+    common_object_pb2 as proto_dot_core_dot_common_dot_common__object__pb2,
+)
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+    name="proto/lib/python/range.proto",
+    package="syft.lib.python",
+    syntax="proto3",
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+    serialized_pb=b'\n\x1cproto/lib/python/range.proto\x12\x0fsyft.lib.python\x1a%proto/core/common/common_object.proto"\x8c\x01\n\x05Range\x12\r\n\x05start\x18\x01 \x01(\x03\x12\x0c\n\x04stop\x18\x02 \x01(\x03\x12\x0c\n\x04step\x18\x03 \x01(\x03\x12\x11\n\thas_start\x18\x04 \x01(\x08\x12\x10\n\x08has_stop\x18\x05 \x01(\x08\x12\x10\n\x08has_step\x18\x06 \x01(\x08\x12!\n\x02id\x18\x07 \x01(\x0b\x32\x15.syft.core.common.UIDb\x06proto3',
+    dependencies=[
+        proto_dot_core_dot_common_dot_common__object__pb2.DESCRIPTOR,
+    ],
+)
+
+
+_RANGE = _descriptor.Descriptor(
+    name="Range",
+    full_name="syft.lib.python.Range",
+    filename=None,
+    file=DESCRIPTOR,
+    containing_type=None,
+    create_key=_descriptor._internal_create_key,
+    fields=[
+        _descriptor.FieldDescriptor(
+            name="start",
+            full_name="syft.lib.python.Range.start",
+            index=0,
+            number=1,
+            type=3,
+            cpp_type=2,
+            label=1,
+            has_default_value=False,
+            default_value=0,
+            message_type=None,
+            enum_type=None,
+            containing_type=None,
+            is_extension=False,
+            extension_scope=None,
+            serialized_options=None,
+            file=DESCRIPTOR,
+            create_key=_descriptor._internal_create_key,
+        ),
+        _descriptor.FieldDescriptor(
+            name="stop",
+            full_name="syft.lib.python.Range.stop",
+            index=1,
+            number=2,
+            type=3,
+            cpp_type=2,
+            label=1,
+            has_default_value=False,
+            default_value=0,
+            message_type=None,
+            enum_type=None,
+            containing_type=None,
+            is_extension=False,
+            extension_scope=None,
+            serialized_options=None,
+            file=DESCRIPTOR,
+            create_key=_descriptor._internal_create_key,
+        ),
+        _descriptor.FieldDescriptor(
+            name="step",
+            full_name="syft.lib.python.Range.step",
+            index=2,
+            number=3,
+            type=3,
+            cpp_type=2,
+            label=1,
+            has_default_value=False,
+            default_value=0,
+            message_type=None,
+            enum_type=None,
+            containing_type=None,
+            is_extension=False,
+            extension_scope=None,
+            serialized_options=None,
+            file=DESCRIPTOR,
+            create_key=_descriptor._internal_create_key,
+        ),
+        _descriptor.FieldDescriptor(
+            name="has_start",
+            full_name="syft.lib.python.Range.has_start",
+            index=3,
+            number=4,
+            type=8,
+            cpp_type=7,
+            label=1,
+            has_default_value=False,
+            default_value=False,
+            message_type=None,
+            enum_type=None,
+            containing_type=None,
+            is_extension=False,
+            extension_scope=None,
+            serialized_options=None,
+            file=DESCRIPTOR,
+            create_key=_descriptor._internal_create_key,
+        ),
+        _descriptor.FieldDescriptor(
+            name="has_stop",
+            full_name="syft.lib.python.Range.has_stop",
+            index=4,
+            number=5,
+            type=8,
+            cpp_type=7,
+            label=1,
+            has_default_value=False,
+            default_value=False,
+            message_type=None,
+            enum_type=None,
+            containing_type=None,
+            is_extension=False,
+            extension_scope=None,
+            serialized_options=None,
+            file=DESCRIPTOR,
+            create_key=_descriptor._internal_create_key,
+        ),
+        _descriptor.FieldDescriptor(
+            name="has_step",
+            full_name="syft.lib.python.Range.has_step",
+            index=5,
+            number=6,
+            type=8,
+            cpp_type=7,
+            label=1,
+            has_default_value=False,
+            default_value=False,
+            message_type=None,
+            enum_type=None,
+            containing_type=None,
+            is_extension=False,
+            extension_scope=None,
+            serialized_options=None,
+            file=DESCRIPTOR,
+            create_key=_descriptor._internal_create_key,
+        ),
+        _descriptor.FieldDescriptor(
+            name="id",
+            full_name="syft.lib.python.Range.id",
+            index=6,
+            number=7,
+            type=11,
+            cpp_type=10,
+            label=1,
+            has_default_value=False,
+            default_value=None,
+            message_type=None,
+            enum_type=None,
+            containing_type=None,
+            is_extension=False,
+            extension_scope=None,
+            serialized_options=None,
+            file=DESCRIPTOR,
+            create_key=_descriptor._internal_create_key,
+        ),
+    ],
+    extensions=[],
+    nested_types=[],
+    enum_types=[],
+    serialized_options=None,
+    is_extendable=False,
+    syntax="proto3",
+    extension_ranges=[],
+    oneofs=[],
+    serialized_start=89,
+    serialized_end=229,
+)
+
+_RANGE.fields_by_name[
+    "id"
+].message_type = proto_dot_core_dot_common_dot_common__object__pb2._UID
+DESCRIPTOR.message_types_by_name["Range"] = _RANGE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+Range = _reflection.GeneratedProtocolMessageType(
+    "Range",
+    (_message.Message,),
+    {
+        "DESCRIPTOR": _RANGE,
+        "__module__": "proto.lib.python.range_pb2"
+        # @@protoc_insertion_point(class_scope:syft.lib.python.Range)
+    },
+)
+_sym_db.RegisterMessage(Range)
+
+
+# @@protoc_insertion_point(module_scope)


Code review comments:
diff_hunk: @@ -0,0 +1,14 @@
+syntax = "proto3";
+
+package syft.lib.python;
+import "proto/core/common/common_object.proto";
+
+message Range {
+  int64 start = 1;
+  int64 stop = 2;
+  int64 step = 3;
+  bool has_start = 4;
path: proto/lib/python/range.proto
text: @user1:
I think this has been introduced in Slices, but it's not necessary at all. You can validate if a field is Set with the `HasField` method. Could you address this and make the change in slices as well? 

@author:
Sure, will do.

diff_hunk: @@ -0,0 +1,36 @@
+# stdlib
+from typing import Any
+from typing import Optional
+from typing import Union
+
+# syft relative
+from ...core.common import UID
+from ...proto.lib.python.range_pb2 import Range as Range_PB
+from .primitive_factory import PyPrimitive
+
+
+class Range(PyPrimitive):
+    def __init__(
+        self,
+        start: Any = None,
path: src/syft/lib/python/range.py
text: @user1:
Shouldn't this be int/Int?

@author:
`mypy` had an issue with `int`, with `Any` it seems to be fine.