Repository: Sigm0oid/dynamodb-geo.py (language: python)

Issue:
Use resource instead of client to save on size of data stored
by using resource instead of client, the type does not need to be included in the data set that is stored in aws, it is instead inferred on insert by aws. 
Use resource instead of client to save on size of data stored
by using resource instead of client, the type does not need to be included in the data set that is stored in aws, it is instead inferred on insert by aws. 
Added get latlng from rectangle query



Discussion / hints:

Fix patch:
diff --git a/.gitignore b/.gitignore
index 0d86e31..f73096b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -136,4 +136,5 @@ dmypy.json
 .pytype/
 
 # Cython debug symbols
-cython_debug/
\ No newline at end of file
+cython_debug/
+.vscode/settings.json
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..1e0b9fe
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,14 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Debug Unit Test",
+            "type": "python",
+            "request": "test",
+            "justMyCode": false,
+        }
+    ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 7c9486d..026499e 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ First you'll need to import the AWS sdk and set up your DynamoDB connection:
 import boto3
 import dynamodbgeo
 import uuid
-dynamodb = boto3.client('dynamodb', region_name='us-east-2')
+dynamodb = boto3.resource('dynamodb', region_name='us-east-2')
 ```
 
 Next you must create an instance of `GeoDataManagerConfiguration` for each geospatial table you wish to interact with. This is a container for various options (see API below), but you must always provide a `DynamoDB` instance and a table name.
@@ -96,9 +96,9 @@ table_util.create_table(create_table_input)
 
 PutItemInput = {
         'Item': {
-            'Country': {'S': "Tunisia"},
-            'Capital': {'S': "Tunis"},
-            'year': {'S': '2020'}
+            'Country': { "Tunisia"},
+            'Capital': { "Tunis"},
+            'year': { '2020'}
         },
         'ConditionExpression': "attribute_not_exists(hashKey)" # ... Anything else to pass through to `putItem`, eg ConditionExpression
 
@@ -127,8 +127,8 @@ UpdateItemDict= { # Dont provide TableName and Key, they are filled in for you
         "UpdateExpression": "set Capital = :val1",
         "ConditionExpression": "Capital = :val2",
         "ExpressionAttributeValues": {
-            ":val1": {"S": "Tunis"},
-            ":val2": {"S": "Ariana"}
+            ":val1": "Tunis",
+            ":val2": "Ariana"
         },
         "ReturnValues": "ALL_NEW"
 }
@@ -168,7 +168,7 @@ Query by rectangle by specifying a `MinPoint` and `MaxPoint`. You can also pass
 QueryRectangleInput={
         "FilterExpression": "Country = :val1",
         "ExpressionAttributeValues": {
-            ":val1": {"S": "Italy"},
+            ":val1": "Italy",
         }
     }
 print(geoDataManager.queryRectangle(
@@ -190,7 +190,7 @@ Same as in query rectangle, you cannot add filtring criteria related to the key
 QueryRadiusInput={
         "FilterExpression": "Country = :val1",
         "ExpressionAttributeValues": {
-            ":val1": {"S": "Italy"},
+            ":val1": "Italy",
         }
     }
 
@@ -272,3 +272,12 @@ The Geohash used in this library is roughly centimeter precision. Therefore, the
 [dynamodb-query]: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html
 [hashkeylength-tests]: https://github.com/rh389/dynamodb-geo.js/blob/master/test/integration/hashKeyLength.ts
 [choosing-hashkeylength]: #choosing-a-hashkeylength-optimising-for-performance-and-cost
+
+### Running Tests locally on MacOS
+
+```sh
+brew install --cask dynamodb-local
+dynamodb-local
+```
+
+With a shell running dynamodb local on port 8000 then the tests can be run!
diff --git a/dynamodbgeo/DynamoDBManager.py b/dynamodbgeo/DynamoDBManager.py
index c029609..3fce75e 100644
--- a/dynamodbgeo/DynamoDBManager.py
+++ b/dynamodbgeo/DynamoDBManager.py
@@ -2,8 +2,9 @@
 Purpose: This class contains all the operation to perform on the DynamoDB table
 
 """
+from boto3.dynamodb.conditions import Attr, Key
+
 from s2 import S2Manager
-from boto3.dynamodb.conditions import Key, Attr
 
 
 class DynamoDBManager:
@@ -16,30 +17,28 @@ def queryGeohash(self, queryInput, hashKey: int, range: int):
         """
         Given a hash key and a min to max GeoHashrange it query the GSI to select the appropriate items to return
         """
-        params=queryInput
+        params = queryInput
+
+        params['IndexName'] = self.config.geohashIndexName
 
-        params['TableName']=self.config.tableName
-        params['IndexName']=self.config.geohashIndexName
-        
         # As eyConditionExpressions must only contain one condition per key, customer passing KeyConditionExpression will be replaced automatically
-        params['KeyConditionExpression']=str(self.config.hashKeyAttributeName) + ' = :hashKey and ' + str(self.config.geohashAttributeName) +' between :geohashMin and :geohashMax'
+        params['KeyConditionExpression'] = str(self.config.hashKeyAttributeName) + ' = :hashKey and ' + str(
+            self.config.geohashAttributeName) + ' between :geohashMin and :geohashMax'
 
         if 'ExpressionAttributeValues' in queryInput.keys():
-            params['ExpressionAttributeValues'].update(  
-                {':hashKey': {'N': str(hashKey)}, ':geohashMax': {
-                    'N': str(range.rangeMax)}, ':geohashMin': {'N': str(range.rangeMin)}}
+            params['ExpressionAttributeValues'].update(
+                {':hashKey': hashKey, ':geohashMax':
+                    int(range.rangeMax), ':geohashMin': int(range.rangeMin)}
             )
         else:
-            params['ExpressionAttributeValues']={':hashKey': {'N': str(hashKey)}, ':geohashMax': {
-                    'N': str(range.rangeMax)}, ':geohashMin': {'N': str(range.rangeMin)}}
-            
-
-        response = self.config.dynamoDBClient.query(**params)
+            params['ExpressionAttributeValues'] = {':hashKey': hashKey, ':geohashMax':
+                                                   int(range.rangeMax), ':geohashMin': int(range.rangeMin)}
+        response = self.config.table.query(**params)
         data = response['Items']
 
-        while 'LastEvaluatedKey' in response: 
-            params['ExclusiveStartKey']=response['LastEvaluatedKey']
-            response = self.config.dynamoDBClient.query(**params)
+        while 'LastEvaluatedKey' in response:
+            params['ExclusiveStartKey'] = response['LastEvaluatedKey']
+            response = self.config.table.query(**params)
             data.extend(response['Items'])
         return data
 
@@ -50,20 +49,18 @@ def put_Point(self, putPointInput: 'PutPointInput'):
         geohash = S2Manager().generateGeohash(putPointInput.GeoPoint)
         hashKey = S2Manager().generateHashKey(geohash, self.config.hashKeyLength)
         response = ""
-        params=putPointInput.ExtraFields.copy()   
+        params = putPointInput.ExtraFields.copy()
 
-        params['TableName']=self.config.tableName
-        
         if('Item' not in putPointInput.ExtraFields.keys()):
-            params['Item']={}
+            params['Item'] = {}
 
-        params['Item'][self.config.hashKeyAttributeName] ={"N": str(hashKey)}
-        params['Item'][self.config.rangeKeyAttributeName] ={"S": putPointInput.RangeKeyValue}
-        params['Item'][self.config.geohashAttributeName] ={'N': str(geohash)}
-        params['Item'][self.config.geoJsonAttributeName] ={"S": "{},{}".format(putPointInput.GeoPoint.latitude,putPointInput.GeoPoint.longitude)}
-        
+        params['Item'][self.config.hashKeyAttributeName] = hashKey
+        params['Item'][self.config.rangeKeyAttributeName] = putPointInput.RangeKeyValue
+        params['Item'][self.config.geohashAttributeName] = geohash
+        params['Item'][self.config.geoJsonAttributeName] = "{},{}".format(
+            putPointInput.GeoPoint.latitude, putPointInput.GeoPoint.longitude)
         try:
-            response = self.config.dynamoDBClient.put_item(**params)
+            response = self.config.table.put_item(Item=params['Item'])
         except Exception as e:
             print("The following error occured during the item insertion :{}".format(e))
             response = "Error"
@@ -77,64 +74,57 @@ def get_Point(self, getPointInput: 'GetPointInput'):
         hashKey = S2Manager().generateHashKey(geohash, self.config.hashKeyLength)
         response = ""
         try:
-            response = self.config.dynamoDBClient.get_item(
-                TableName=self.config.tableName,
+            response = self.config.table.get_item(
                 Key={
-                    self.config.hashKeyAttributeName: {"N": str(hashKey)},
-                    self.config.rangeKeyAttributeName: {
-                        "S": getPointInput.RangeKeyValue}
+                    self.config.hashKeyAttributeName: hashKey,
+                    self.config.rangeKeyAttributeName: getPointInput.RangeKeyValue
                 }
             )
         except Exception as e:
             print("The following error occured during the item retrieval :{}".format(e))
             response = "Error"
         return response
-    
-    def update_Point(self,UpdateItemInput : 'UpdateItemInput'):
+
+    def update_Point(self, UpdateItemInput: 'UpdateItemInput'):
         """
         The dict in Item Update call, should contains a dict with string as a key and a string as a value: {"N": "123"}
         """
         geohash = S2Manager().generateGeohash(UpdateItemInput.GeoPoint)
         hashKey = S2Manager().generateHashKey(geohash, self.config.hashKeyLength)
         response = ""
-        params=UpdateItemInput.ExtraFields.copy()   
+        params = UpdateItemInput.ExtraFields.copy()
 
-        params['TableName']=self.config.tableName
-        
         if('Key' not in UpdateItemInput.ExtraFields.keys()):
-            params['Key']={}
+            params['Key'] = {}
+
+        params['Key'][self.config.hashKeyAttributeName] = hashKey
+        params['Key'][self.config.rangeKeyAttributeName] = UpdateItemInput.RangeKeyValue
 
-        params['Key'][self.config.hashKeyAttributeName] ={"N": str(hashKey)}
-        params['Key'][self.config.rangeKeyAttributeName] ={"S": UpdateItemInput.RangeKeyValue}
-        
-        #TODO Geohash and geoJson cannot be updated. For now no control over that need to be added        
+        # TODO Geohash and geoJson cannot be updated. For now no control over that need to be added
         try:
-            response = self.config.dynamoDBClient.update_item(**params)
+            response = self.config.table.update_item(**params)
         except Exception as e:
             print("The following error occured during the item update :{}".format(e))
             response = "Error"
         return response
 
-    def delete_Point(self,DeleteItemInput : 'DeleteItemInput'):
+    def delete_Point(self, DeleteItemInput: 'DeleteItemInput'):
         """
         The dict in Item Update call, should contains a dict with string as a key and a string as a value: {"N": "123"}
         """
         geohash = S2Manager().generateGeohash(DeleteItemInput.GeoPoint)
         hashKey = S2Manager().generateHashKey(geohash, self.config.hashKeyLength)
         response = ""
-        params=DeleteItemInput.ExtraFields.copy()   
+        params = DeleteItemInput.ExtraFields.copy()
 
-        params['TableName']=self.config.tableName
-        
         if('Key' not in DeleteItemInput.ExtraFields.keys()):
-            params['Key']={}
+            params['Key'] = {}
 
-        params['Key'][self.config.hashKeyAttributeName] ={"N": str(hashKey)}
-        params['Key'][self.config.rangeKeyAttributeName] ={"S": DeleteItemInput.RangeKeyValue}
+        params['Key'][self.config.hashKeyAttributeName] = hashKey
+        params['Key'][self.config.rangeKeyAttributeName] = DeleteItemInput.RangeKeyValue
         try:
-            response = self.config.dynamoDBClient.delete_item(**params)
+            response = self.config.table.delete_item(**params)
         except Exception as e:
             print("The following error occured during the item delete :{}".format(e))
             response = "Error"
         return response
-
diff --git a/dynamodbgeo/GeoDataManager.py b/dynamodbgeo/GeoDataManager.py
index b2b4751..1e9a70d 100644
--- a/dynamodbgeo/GeoDataManager.py
+++ b/dynamodbgeo/GeoDataManager.py
@@ -2,9 +2,8 @@
 """
 Purpose: A wrapper on the top of DynamoDBManager for performing CRUD operation on our DynamoBD table
 """
-import GeoDataManagerConfiguration
 from DynamoDBManager import DynamoDBManager
-from model import PutPointInput, Covering, GetPointInput
+from model import Covering
 from s2 import *
 from s2sphere import LatLng as S2LatLng
 EARTH_RADIUS_METERS = 6367000.0
@@ -62,14 +61,15 @@ def queryRadius(self, QueryRadiusInput: 'QueryRadiusRequest'):
             centerLatLng = S2LatLng.from_degrees(QueryRadiusInput.getCenterPoint(
             ).getLatitude(), QueryRadiusInput.getCenterPoint().getLongitude())
             for item in filtered_results:
-                geoJson = item[self.config.geoJsonAttributeName]["S"]
+                geoJson = item[self.config.geoJsonAttributeName]
                 coordinates = geoJson.split(",")
                 latitude = float(coordinates[0])
                 longitude = float(coordinates[1])
                 latLng = S2LatLng.from_degrees(latitude, longitude)
                 tuples.append((centerLatLng.get_distance(
                     latLng).radians * EARTH_RADIUS_METERS, item))
-            tuples.sort(key=lambda x: x[0])  # Sort the list by distance (x [0] is the distance)
+            # Sort the list by distance (x [0] is the distance)
+            tuples.sort(key=lambda x: x[0])
             return [item[1] for item in tuples]
         else:
             return filtered_results
@@ -80,7 +80,7 @@ def filterByRadius(self, ItemList: 'points retrieved from dynamoDB', QueryRadius
         radiusInMeter = QueryRadiusInput.getRadiusInMeter()
         result = []
         for item in ItemList:
-            geoJson = item[self.config.geoJsonAttributeName]["S"]
+            geoJson = item[self.config.geoJsonAttributeName]
             coordinates = geoJson.split(",")
             latitude = float(coordinates[0])
             longitude = float(coordinates[1])
@@ -94,7 +94,7 @@ def filterByRectangle(self, ItemList: 'points retrieved from dynamoDB', QueryRec
             QueryRectangleInput)
         result = []
         for item in ItemList:
-            geoJson = item[self.config.geoJsonAttributeName]["S"]
+            geoJson = item[self.config.geoJsonAttributeName]
             coordinates = geoJson.split(",")
             latitude = float(coordinates[0])
             longitude = float(coordinates[1])
diff --git a/dynamodbgeo/GeoDataManagerConfiguration.py b/dynamodbgeo/GeoDataManagerConfiguration.py
index 31f5f80..785a3bb 100644
--- a/dynamodbgeo/GeoDataManagerConfiguration.py
+++ b/dynamodbgeo/GeoDataManagerConfiguration.py
@@ -1,6 +1,6 @@
 
 """
-Purpose: A signle point of entry for all the table and dynamodb client configuration
+Purpose: A signle point of entry for all the table and dynamodb resource configuration
 """
 from s2sphere import RegionCoverer as S2RegionCoverer
 
@@ -11,9 +11,10 @@ class GeoDataManagerConfiguration:
 
     geohashIndexName = "geohash-index"  # name of the LSI
 
-    def __init__(self, dynamoDBClient: 'dynamoDB client', tableName: str):
-        self.dynamoDBClient = dynamoDBClient  # dynamodb client taken from aws sdk
+    def __init__(self, tableName: str, dynamodbResource):
         self.tableName = tableName
+        self.dynamodbResource = dynamodbResource
+        self.table = dynamodbResource.Table(tableName)
         self.S2RegionCoverer = S2RegionCoverer  # this is form the s2 library
         self.hashKeyAttributeName = "hashKey"
         self.rangeKeyAttributeName = "rangeKey"
diff --git a/dynamodbgeo/model/Covering.py b/dynamodbgeo/model/Covering.py
index f436921..5979413 100644
--- a/dynamodbgeo/model/Covering.py
+++ b/dynamodbgeo/model/Covering.py
@@ -1,7 +1,8 @@
-import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))
-
-from GeohashRange import GeohashRange
 from s2sphere import CellId as S2CellId
+from GeohashRange import GeohashRange
+import os
+import sys
+sys.path.append(os.path.dirname(os.path.realpath(__file__)))
 
 
 class Covering:
diff --git a/dynamodbgeo/s2/S2Util.py b/dynamodbgeo/s2/S2Util.py
index 92d8236..bc0a681 100644
--- a/dynamodbgeo/s2/S2Util.py
+++ b/dynamodbgeo/s2/S2Util.py
@@ -32,8 +32,8 @@ def getBoundingLatLngRectFromQueryRadiusInput(self, QueryRadiusRequest: 'QueryRa
             (centerLatLng.get_distance(latReferenceLatLng).radians * EARTH_RADIUS_METERS)
         lngForRadius = radiusInMeter / \
             (centerLatLng.get_distance(lngReferenceLatLng).radians * EARTH_RADIUS_METERS)
-        minLatLng = S2LatLng.from_degrees(centerPoint.getLatitude() - latForRadius,
-                                          centerPoint.getLongitude() - lngForRadius)
-        maxLatLng = S2LatLng.from_degrees(centerPoint.getLatitude() + latForRadius,
-                                          centerPoint.getLongitude() + lngForRadius)
+        minLatLng = S2LatLng.from_degrees(max(centerPoint.getLatitude() - latForRadius, -90),
+                                          max(centerPoint.getLongitude() - lngForRadius, -180))
+        maxLatLng = S2LatLng.from_degrees(min(centerPoint.getLatitude() + latForRadius, 90),
+                                          min(centerPoint.getLongitude() + lngForRadius, 180))
         return S2LatLngRect(minLatLng, maxLatLng)
diff --git a/dynamodbgeo/util/GeoTableUtil.py b/dynamodbgeo/util/GeoTableUtil.py
index 600fae3..7674bfe 100644
--- a/dynamodbgeo/util/GeoTableUtil.py
+++ b/dynamodbgeo/util/GeoTableUtil.py
@@ -10,73 +10,78 @@
 01-create-table.py - https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DAX.client.run-application-python.01-create-table.html
 
 
-Author: Hamza Rhibi
+Author(s): Hamza Rhibi, Brandon Sneider
 """
 
+
 class GeoTableUtil:
 
-    def __init__(self,config):
-        self.config=config
-    
+    def __init__(self, config):
+        self.config = config
+
     def getCreateTableRequest(self):
         """
         Prepare dict with default parameter to create the base
         """
         params = {
-            'TableName' : self.config.tableName,
-            'KeySchema': [       
-                { 'AttributeName': self.config.hashKeyAttributeName, 'KeyType': "HASH"},    # Partition key
-                { 'AttributeName': self.config.rangeKeyAttributeName, 'KeyType': "RANGE" }   # Sort key
+            'TableName': self.config.tableName,
+            'KeySchema': [
+                {'AttributeName': self.config.hashKeyAttributeName,
+                    'KeyType': "HASH"},    # Partition key
+                {'AttributeName': self.config.rangeKeyAttributeName,
+                    'KeyType': "RANGE"}   # Sort key
             ],
-            'AttributeDefinitions':[
-                { 'AttributeName': self.config.hashKeyAttributeName, 'AttributeType': 'N' },
-                { 'AttributeName': self.config.rangeKeyAttributeName, 'AttributeType': 'S' },
-                { 'AttributeName': self.config.geohashAttributeName, 'AttributeType': 'N' }
+            'AttributeDefinitions': [
+                {'AttributeName': self.config.hashKeyAttributeName,
+                    'AttributeType': 'N'},
+                {'AttributeName': self.config.rangeKeyAttributeName,
+                    'AttributeType': 'S'},
+                {'AttributeName': self.config.geohashAttributeName, 'AttributeType': 'N'}
 
             ],
-            'LocalSecondaryIndexes':[
+            'LocalSecondaryIndexes': [
                 {
-                'IndexName': self.config.geohashIndexName,
-                'KeySchema': [
-                    {
-                    'KeyType': 'HASH',
-                    'AttributeName': self.config.hashKeyAttributeName
-                    },
-                    {
-                    'KeyType': 'RANGE',
-                    'AttributeName': self.config.geohashAttributeName
+                    'IndexName': self.config.geohashIndexName,
+                    'KeySchema': [
+                        {
+                            'KeyType': 'HASH',
+                            'AttributeName': self.config.hashKeyAttributeName
+                        },
+                        {
+                            'KeyType': 'RANGE',
+                            'AttributeName': self.config.geohashAttributeName
+                        }
+                    ],
+                    'Projection': {
+                        'ProjectionType': 'ALL'
                     }
-                ],
-                'Projection': {
-                    'ProjectionType': 'ALL'
-                }
                 }
             ],
-            'ProvisionedThroughput': {       
-                'ReadCapacityUnits': 10, 
+            'ProvisionedThroughput': {
+                'ReadCapacityUnits': 10,
                 'WriteCapacityUnits': 10
             }
         }
         return params
 
-
-    def create_table(self,CreateTableInput :"Dict with parameters to create the table"):
+    def create_table(self, CreateTableInput: "Dict with parameters to create the table"):
         # skip if table already exists
         try:
-            response = self.config.dynamoDBClient.describe_table(TableName=self.config.tableName)
+            response = self.config.dynamodbResource.describe_table(
+                TableName=self.config.tableName)
             # table exists...bail
-            print ("Table [{}] already exists. Skipping table creation.".format(self.config.tableName))
+            print("Table [{}] already exists. Skipping table creation.".format(
+                self.config.tableName))
             return
         except:
-            pass # no table... good
-        print ("Creating table [{}]".format(self.config.tableName))
+            pass  # no table... good
+        print("Creating table [{}]".format(self.config.tableName))
 
-        table = self.config.dynamoDBClient.create_table(**CreateTableInput)
+        table = self.config.dynamodbResource.create_table(**CreateTableInput)
 
-        print ("Waiting for table [{}] to be created".format(self.config.tableName))
-        self.config.dynamoDBClient.get_waiter('table_exists').wait(TableName=self.config.tableName)
+        print("Waiting for table [{}] to be created".format(
+            self.config.tableName))
+        table.wait_until_exists()
         # if no exception, continue
-        print ("Table created")
+        print("Table created")
         return
-
-      
\ No newline at end of file
diff --git a/entry_script.py b/entry_script.py
index 3969fae..c0c874e 100644
--- a/entry_script.py
+++ b/entry_script.py
@@ -8,8 +8,9 @@
 
 if __name__ == "__main__":
     # initiat dynamodb service ressource,
-    dynamodb = boto3.client('dynamodb', region_name='us-east-2')
-    config = dynamodbgeo.GeoDataManagerConfiguration(dynamodb, 'geo_test_8')
+    dynamodbResource = boto3.resource('dynamodb', region_name='us-east-2')
+    config = dynamodbgeo.GeoDataManagerConfiguration(
+        'geo_test_8', dynamodbResource)
     geoDataManager = dynamodbgeo.GeoDataManager(config)
 
     table_util = dynamodbgeo.GeoTableUtil(config)
@@ -24,9 +25,9 @@
     # define a dict of the item to input
     PutItemInput = {
         'Item': {
-            'Country': {'S': "Italy"},
-            'Capital': {'S': "Tunis"},
-            'year': {'S': '2020'}
+            'Country': "Italy",
+            'Capital': "Tunis",
+            'year': '2020'
         },
         # ... Anything else to pass through to `putItem`, eg ConditionExpression
         'ConditionExpression': "attribute_not_exists(hashKey)"
@@ -59,7 +60,7 @@
     QueryRectangleInput = {
         "FilterExpression": "Country = :val1",
         "ExpressionAttributeValues": {
-            ":val1": {"S": "Italy"},
+            ":val1": "Italy",
         }
     }
     # testing the query rectangle method
@@ -90,7 +91,7 @@
     QueryRadiusInput = {
         "FilterExpression": "Country = :val1",
         "ExpressionAttributeValues": {
-            ":val1": {"S": "Italy"},
+            ":val1": "Italy",
         }
     }
 
@@ -104,8 +105,8 @@
         "UpdateExpression": "set Capital = :val1",
         "ConditionExpression": "Capital = :val2",
         "ExpressionAttributeValues": {
-            ":val1": {"S": "Tunis"},
-            ":val2": {"S": "Ariana"}
+            ":val1": "Tunis",
+            ":val2": "Ariana"
         },
         "ReturnValues": "ALL_NEW"
     }
diff --git a/requirements.txt b/requirements.txt
index 17d87a6..e3571ca 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,3 @@
 boto3==1.11.8
 s2sphere==0.2.5
+pytruth
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 41b98c1..36c16bd 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,7 @@
 from setuptools import setup
 import setuptools
+
+
 def readme():
     with open('README.md') as f:
         README = f.read()
@@ -7,21 +9,21 @@ def readme():
 
 
 setup(
-      name="dynamodbgeo",
-      version='0.0.3',
-      description='A python port of awslabs/dynamodb-geo, for easier geospatial data manipulation and querying in DynamoDB',
-      url='https://github.com/Sigm0oid/dynamodb-geo.py',
-      author='Hamza Rhibi & Walid Sadallah',
-      author_email='elrhibihamzas@gmail.com',
-      license='MIT',
-      long_description=readme(),
-      long_description_content_type="text/markdown",
-      classifiers=[
-            "License :: OSI Approved :: MIT License",
+    name="dynamodbgeo",
+    version='0.0.4',
+    description='A python port of awslabs/dynamodb-geo, for easier geospatial data manipulation and querying in DynamoDB',
+    url='https://github.com/Sigm0oid/dynamodb-geo.py',
+    author='Hamza Rhibi & Walid Sadallah & Brandon Sneider',
+    author_email='elrhibihamzas@gmail.com',
+    license='MIT',
+    long_description=readme(),
+    long_description_content_type="text/markdown",
+    classifiers=[
+        "License :: OSI Approved :: MIT License",
             "Programming Language :: Python :: 3",
             "Programming Language :: Python :: 3.7",
-      ],
-      packages=setuptools.find_packages(),
-      include_package_data=True,
-      install_requires=["boto3>=1.11.8","s2sphere>=0.2.5"],
-      )
+    ],
+    packages=setuptools.find_packages(),
+    include_package_data=True,
+    install_requires=["boto3>=1.11.8", "s2sphere>=0.2.5"],
+)


Test patch:
diff --git a/tests/test_create_table.py b/tests/test_create_table.py
index f751375..7fc1cdf 100644
--- a/tests/test_create_table.py
+++ b/tests/test_create_table.py
@@ -1,25 +1,25 @@
-import dynamodbgeo
-from vars import dynamodb
 import uuid
 
+import boto3
+import dynamodbgeo
+from dynamodbgeo.util import GeoTableUtil
+from truth.truth import AssertThat
+
+from vars import dynamodbResource
+
 
 def test_create_table():
-    try:
-        table_name = str(uuid.uuid4())
-        config = dynamodbgeo.GeoDataManagerConfiguration(
-            dynamodb, table_name)
-        geoDataManager = dynamodbgeo.GeoDataManager(config)
-        table_util = dynamodbgeo.GeoTableUtil(config)
-        create_table_input = table_util.getCreateTableRequest()
-        # tweaking the base table parameters
-        create_table_input["ProvisionedThroughput"]['ReadCapacityUnits'] = 5
-        # pass the input to create_table method
-        table_util.create_table(create_table_input)
-        response = dynamodb.list_tables(
-        )
-        if table_name not in response["TableNames"]:
-            assert False
-        else:
-            assert True
-    except:
-        assert False
+    table_name = str(uuid.uuid4())
+    config = dynamodbgeo.GeoDataManagerConfiguration(
+        table_name, dynamodbResource)
+    table_util = GeoTableUtil(config)
+    create_table_input = table_util.getCreateTableRequest()
+    # tweaking the base table parameters
+    create_table_input["ProvisionedThroughput"]['ReadCapacityUnits'] = 5
+    # pass the input to create_table method
+    table_util.create_table(create_table_input)
+    dynamodb = boto3.client('dynamodb', endpoint_url="http://localhost:8000",
+                            region_name='local')
+
+    response = dynamodb.list_tables()
+    AssertThat(response['TableNames']).Contains(table_name)
diff --git a/tests/test_get_point.py b/tests/test_get_point.py
index 2ff64a2..c8b3df7 100644
--- a/tests/test_get_point.py
+++ b/tests/test_get_point.py
@@ -1,43 +1,45 @@
-import dynamodbgeo
-from vars import dynamodb
 import uuid
 
+from truth.truth import AssertThat
+
+import dynamodbgeo
+from dynamodbgeo.util import GeoTableUtil
+
+from vars import dynamodbResource
+
 
 def test_get_point():
-    try:
-        table_name = str(uuid.uuid4())
-        config = dynamodbgeo.GeoDataManagerConfiguration(
-            dynamodb, table_name)
-        geoDataManager = dynamodbgeo.GeoDataManager(config)
-        table_util = dynamodbgeo.GeoTableUtil(config)
-        create_table_input = table_util.getCreateTableRequest()
-        # tweaking the base table parameters
-        create_table_input["ProvisionedThroughput"]['ReadCapacityUnits'] = 5
-        # pass the input to create_table method
-        table_util.create_table(create_table_input)
-        # define a dict of the item to input
-        PutItemInput = {
-            'Item': {
-                'Country': {'S': "Italy"},
-                'Capital': {'S': "Tunis"},
-                'year': {'S': '2020'}
-            },
-            # ... Anything else to pass through to `putItem`, eg ConditionExpression
-            'ConditionExpression': "attribute_not_exists(hashKey)"
+    table_name = str(uuid.uuid4())
+    config = dynamodbgeo.GeoDataManagerConfiguration(
+        table_name, dynamodbResource)
+    geoDataManager = dynamodbgeo.GeoDataManager(config)
+    table_util = GeoTableUtil(config)
+    create_table_input = table_util.getCreateTableRequest()
+    # tweaking the base table parameters
+    create_table_input["ProvisionedThroughput"]['ReadCapacityUnits'] = 5
+    # pass the input to create_table method
+    table_util.create_table(create_table_input)
+    # define a dict of the item to input
+    PutItemInput = {
+        'Item': {
+            'Country': "Italy",
+            'Capital': "Tunis",
+            'year': '2020'
+        },
+        # ... Anything else to pass through to `putItem`, eg ConditionExpression
+        'ConditionExpression': "attribute_not_exists(hashKey)"
 
-        }
-        range_key = "b385bbf9-581b-4df4-b5ad-4c0e3a0794b6"
-        cords = (36.879163, 10.243120)
-        response = geoDataManager.put_Point(dynamodbgeo.PutPointInput(
+    }
+    range_key = "b385bbf9-581b-4df4-b5ad-4c0e3a0794b6"
+    cords = (36.879163, 10.243120)
+    response = geoDataManager.put_Point(dynamodbgeo.PutPointInput(
+        dynamodbgeo.GeoPoint(cords[0], cords[1]),
+        range_key,
+        PutItemInput
+    ))
+    response = geoDataManager.get_Point(
+        dynamodbgeo.GetPointInput(
             dynamodbgeo.GeoPoint(cords[0], cords[1]),
-            range_key,
-            PutItemInput
+            range_key
         ))
-        response = geoDataManager.get_Point(
-            dynamodbgeo.GetPointInput(
-                dynamodbgeo.GeoPoint(cords[0], cords[1]),
-                range_key
-            ))
-        assert response != "Error"
-    except:
-        assert False
+    AssertThat(response).IsNotEqualTo("Error")
diff --git a/tests/test_put_point.py b/tests/test_put_point.py
index 66bfe85..158ec46 100644
--- a/tests/test_put_point.py
+++ b/tests/test_put_point.py
@@ -1,15 +1,18 @@
-import dynamodbgeo
-from vars import dynamodb
 import uuid
 
+import dynamodbgeo
+from dynamodbgeo.util import GeoTableUtil
+
+from vars import dynamodbResource
+
 
 def test_put_point():
     try:
         table_name = str(uuid.uuid4())
         config = dynamodbgeo.GeoDataManagerConfiguration(
-            dynamodb, table_name)
+            table_name, dynamodbResource)
         geoDataManager = dynamodbgeo.GeoDataManager(config)
-        table_util = dynamodbgeo.GeoTableUtil(config)
+        table_util = GeoTableUtil(config)
         create_table_input = table_util.getCreateTableRequest()
         # tweaking the base table parameters
         create_table_input["ProvisionedThroughput"]['ReadCapacityUnits'] = 5
@@ -18,9 +21,9 @@ def test_put_point():
         # define a dict of the item to input
         PutItemInput = {
             'Item': {
-                'Country': {'S': "Italy"},
-                'Capital': {'S': "Tunis"},
-                'year': {'S': '2020'}
+                'Country': "Italy",
+                'Capital': "Tunis",
+                'year': '2020'
             },
             # ... Anything else to pass through to `putItem`, eg ConditionExpression
             'ConditionExpression': "attribute_not_exists(hashKey)"
diff --git a/tests/test_query_radius.py b/tests/test_query_radius.py
index 9d32f42..1e2aa53 100644
--- a/tests/test_query_radius.py
+++ b/tests/test_query_radius.py
@@ -1,15 +1,41 @@
-import dynamodbgeo
-from vars import dynamodb
 import uuid
 
+import dynamodbgeo
+from dynamodbgeo.util import GeoTableUtil
+from truth.truth import AssertThat
+from vars import dynamodbResource
+
+
+def test_query_radius_noissuearoundEurope():
+    table_name = str(uuid.uuid4())
+    config = dynamodbgeo.GeoDataManagerConfiguration(
+        table_name, dynamodbResource)
+    geoDataManager = dynamodbgeo.GeoDataManager(config)
+    table_util = GeoTableUtil(config)
+    create_table_input = table_util.getCreateTableRequest()
+    # tweaking the base table parameters
+    create_table_input["ProvisionedThroughput"]['ReadCapacityUnits'] = 5
+    # pass the input to create_table method
+    table_util.create_table(create_table_input)
+
+    # "lat": "32.8007718436974",
+    # "lng": "10.107633337736274",
+    # "radius": "5836769.2553483695",
+
+    result = geoDataManager.queryRadius(
+        dynamodbgeo.QueryRadiusRequest(
+            dynamodbgeo.GeoPoint(32.8007718436974, 10.107633337736274),
+            5836769.2553483695, {}))
+    AssertThat(result).IsEmpty()
+
 
 def test_query_radius():
     try:
         table_name = str(uuid.uuid4())
         config = dynamodbgeo.GeoDataManagerConfiguration(
-            dynamodb, table_name)
+            table_name, dynamodbResource)
         geoDataManager = dynamodbgeo.GeoDataManager(config)
-        table_util = dynamodbgeo.GeoTableUtil(config)
+        table_util = GeoTableUtil(config)
         create_table_input = table_util.getCreateTableRequest()
         # tweaking the base table parameters
         create_table_input["ProvisionedThroughput"]['ReadCapacityUnits'] = 5
@@ -17,9 +43,9 @@ def test_query_radius():
         table_util.create_table(create_table_input)
         PutItemInput = {
             'Item': {
-                'Country': {'S': "Italy"},
-                'Capital': {'S': "Tunis"},
-                'year': {'S': '2020'}
+                'Country': "Italy",
+                'Capital': "Tunis",
+                'year': '2020'
             },
             # ... Anything else to pass through to `putItem`, eg ConditionExpression
             'ConditionExpression': "attribute_not_exists(hashKey)"
@@ -36,14 +62,14 @@ def test_query_radius():
         QueryRadiusInput = {
             "FilterExpression": "Country = :val1",
             "ExpressionAttributeValues": {
-                ":val1": {"S": "Italy"},
+                ":val1": "Italy",
             }
         }
         result = geoDataManager.queryRadius(
             dynamodbgeo.QueryRadiusRequest(
                 dynamodbgeo.GeoPoint(36.542840, 9.662671),
                 200, QueryRadiusInput))
-        if len(result) != 1 or result[0]["rangeKey"]["S"] != "inside":
+        if len(result) != 1 or result[0]["rangeKey"] != "inside":
             assert False
         assert True
     except:
diff --git a/tests/test_query_radius_sort.py b/tests/test_query_radius_sort.py
index 6b3d35c..6610c1f 100644
--- a/tests/test_query_radius_sort.py
+++ b/tests/test_query_radius_sort.py
@@ -1,53 +1,55 @@
-import dynamodbgeo
-from vars import dynamodb
 import uuid
 
+import dynamodbgeo
+from dynamodbgeo.util import GeoTableUtil
+from truth.truth import AssertThat
+
+from vars import dynamodbResource
+
 
 def test_query_radius_sort():
-    try:
-        table_name = str(uuid.uuid4())
-        config = dynamodbgeo.GeoDataManagerConfiguration(
-            dynamodb, table_name)
-        geoDataManager = dynamodbgeo.GeoDataManager(config)
-        table_util = dynamodbgeo.GeoTableUtil(config)
-        create_table_input = table_util.getCreateTableRequest()
-        # tweaking the base table parameters
-        create_table_input["ProvisionedThroughput"]['ReadCapacityUnits'] = 5
-        # pass the input to create_table method
-        table_util.create_table(create_table_input)
-        PutItemInput = {
-            'Item': {
-                'Country': {'S': "Italy"},
-                'Capital': {'S': "Tunis"},
-                'year': {'S': '2020'}
-            },
-            # ... Anything else to pass through to `putItem`, eg ConditionExpression
-            'ConditionExpression': "attribute_not_exists(hashKey)"
-        }
-        geoDataManager.put_Point(dynamodbgeo.PutPointInput(
-            dynamodbgeo.GeoPoint(36.874419, 10.241062),
-            "1",
-            PutItemInput))
-        geoDataManager.put_Point(dynamodbgeo.PutPointInput(
-            dynamodbgeo.GeoPoint(36.874507, 10.240857),
-            "2",
-            PutItemInput))
-        geoDataManager.put_Point(dynamodbgeo.PutPointInput(
-            dynamodbgeo.GeoPoint(36.874617, 10.241441),
-            "3",
-            PutItemInput))
-        QueryRadiusInput = {
-            "FilterExpression": "Country = :val1",
-            "ExpressionAttributeValues": {
-                ":val1": {"S": "Italy"},
-            }
+    table_name = str(uuid.uuid4())
+    config = dynamodbgeo.GeoDataManagerConfiguration(
+        table_name, dynamodbResource)
+    geoDataManager = dynamodbgeo.GeoDataManager(config)
+    table_util = GeoTableUtil(config)
+    create_table_input = table_util.getCreateTableRequest()
+    # tweaking the base table parameters
+    create_table_input["ProvisionedThroughput"]['ReadCapacityUnits'] = 5
+    # pass the input to create_table method
+    table_util.create_table(create_table_input)
+    PutItemInput = {
+        'Item': {
+            'Country': "Italy",
+            'Capital': "Tunis",
+            'year': '2020'
+        },
+        # ... Anything else to pass through to `putItem`, eg ConditionExpression
+        'ConditionExpression': "attribute_not_exists(hashKey)"
+    }
+    geoDataManager.put_Point(dynamodbgeo.PutPointInput(
+        dynamodbgeo.GeoPoint(36.874419, 10.241062),
+        "1",
+        PutItemInput))
+    geoDataManager.put_Point(dynamodbgeo.PutPointInput(
+        dynamodbgeo.GeoPoint(36.874507, 10.240857),
+        "2",
+        PutItemInput))
+    geoDataManager.put_Point(dynamodbgeo.PutPointInput(
+        dynamodbgeo.GeoPoint(36.874617, 10.241441),
+        "3",
+        PutItemInput))
+    QueryRadiusInput = {
+        "FilterExpression": "Country = :val1",
+        "ExpressionAttributeValues": {
+            ":val1": "Italy",
         }
-        result = geoDataManager.queryRadius(
-            dynamodbgeo.QueryRadiusRequest(
-                dynamodbgeo.GeoPoint(36.874444, 10.241059),
-                95, QueryRadiusInput, sort=True))
-        if len(result) != 3 or result[0]["rangeKey"]["S"] != "1" or result[1]["rangeKey"]["S"] != "2" or result[2]["rangeKey"]["S"] != "3":
-            assert False
-        assert True
-    except:
-        assert False
+    }
+    result = geoDataManager.queryRadius(
+        dynamodbgeo.QueryRadiusRequest(
+            dynamodbgeo.GeoPoint(36.874444, 10.241059),
+            95, QueryRadiusInput, sort=True))
+    AssertThat(len(result)).IsEqualTo(3)
+    AssertThat(result[0]['rangeKey']).IsEqualTo("1")
+    AssertThat(result[1]['rangeKey']).IsEqualTo("2")
+    AssertThat(result[2]['rangeKey']).IsEqualTo("3")
diff --git a/tests/test_query_rectangle.py b/tests/test_query_rectangle.py
index b725b5f..f55b92a 100644
--- a/tests/test_query_rectangle.py
+++ b/tests/test_query_rectangle.py
@@ -1,15 +1,18 @@
-import dynamodbgeo
-from vars import dynamodb
 import uuid
 
+import dynamodbgeo
+from dynamodbgeo.util import GeoTableUtil
+
+from vars import dynamodbResource
+
 
 def test_query_rectangle():
     try:
         table_name = str(uuid.uuid4())
         config = dynamodbgeo.GeoDataManagerConfiguration(
-            dynamodb, table_name)
+            table_name, dynamodbResource)
         geoDataManager = dynamodbgeo.GeoDataManager(config)
-        table_util = dynamodbgeo.GeoTableUtil(config)
+        table_util = GeoTableUtil(config)
         create_table_input = table_util.getCreateTableRequest()
         # tweaking the base table parameters
         create_table_input["ProvisionedThroughput"]['ReadCapacityUnits'] = 5
@@ -17,9 +20,9 @@ def test_query_rectangle():
         table_util.create_table(create_table_input)
         PutItemInput = {
             'Item': {
-                'Country': {'S': "Italy"},
-                'Capital': {'S': "Tunis"},
-                'year': {'S': '2020'}
+                'Country': "Italy",
+                'Capital': "Tunis",
+                'year': '2020'
             },
             # ... Anything else to pass through to `putItem`, eg ConditionExpression
             'ConditionExpression': "attribute_not_exists(hashKey)"
@@ -36,14 +39,14 @@ def test_query_rectangle():
         QueryRectangleInput = {
             "FilterExpression": "Country = :val1",
             "ExpressionAttributeValues": {
-                ":val1": {"S": "Italy"},
+                ":val1": "Italy",
             }
         }
         result = geoDataManager.queryRectangle(
             dynamodbgeo.QueryRectangleRequest(
                 dynamodbgeo.GeoPoint(36.878184, 10.242358),
                 dynamodbgeo.GeoPoint(36.879317, 10.243648), QueryRectangleInput))
-        if len(result) != 1 or result[0]["rangeKey"]["S"] != "inside":
+        if len(result) != 1 or result[0]["rangeKey"] != "inside":
             assert False
         assert True
     except:
diff --git a/tests/test_s2util.py b/tests/test_s2util.py
new file mode 100644
index 0000000..154347b
--- /dev/null
+++ b/tests/test_s2util.py
@@ -0,0 +1,47 @@
+import dynamodbgeo
+from dynamodbgeo.s2 import S2Util
+from s2sphere.sphere import LatLng, LatLngRect
+from truth.truth import AssertThat
+
+
+def test_s2util_getBoundingLatLngRectFromQueryRadiusInput():
+    qri = dynamodbgeo.QueryRadiusRequest(
+        dynamodbgeo.GeoPoint(36.874444, 10.241059),
+        95, {}, sort=True)
+    latLngRect = S2Util().getBoundingLatLngRectFromQueryRadiusInput(
+        qri)
+    AssertThat(latLngRect).IsNotNone()
+
+
+def test_S2LatLngRect_NewZealand2():
+    p1_conv = LatLng.from_degrees(-52.71152508854678, 157.99825332608728)
+    print(p1_conv)
+    p2_conv = LatLng.from_degrees(-31.329586226597584, 179.77995299846089)
+    print(p2_conv)
+
+    rect = LatLngRect(p1_conv,
+                      p2_conv)
+    AssertThat(rect).IsNotNone()
+# It fails bc the max long is 180 and max lat is 90 so as logn as less than 180 it will work, why does the radius query return a long of 186??
+
+
+def test_S2LatLngRect_NewZealand():
+    rect = LatLngRect(LatLng.from_degrees(-52.71152508854678, 157.99825332608728),
+                      LatLng.from_degrees(-31.329586226597584, 180.0))
+    AssertThat(rect).IsNotNone()
+
+
+def test_s2util_getBoundingLatLngRectFromQueryRadiusInput_NewZealand():
+    # "pathParameters": {
+    #     "lat": "-42.02055565757218",
+    #     "lng": "172.38910316227407",
+    #     "radius": "1188035.19116924",
+    #     "proxy": "/path/to/resource"
+    # },
+
+    qri = dynamodbgeo.QueryRadiusRequest(
+        dynamodbgeo.GeoPoint(-42.02055565757218, 172.38910316227407),
+        1188035.19116924, {}, sort=True)
+    latLngRect = S2Util().getBoundingLatLngRectFromQueryRadiusInput(
+        qri)
+    AssertThat(latLngRect).IsNotNone()
diff --git a/tests/vars.py b/tests/vars.py
index 388d9fd..076e274 100644
--- a/tests/vars.py
+++ b/tests/vars.py
@@ -1,5 +1,6 @@
 import boto3
 from botocore.config import Config
 
-dynamodb = boto3.client('dynamodb', endpoint_url="http://localhost:8000",
-                        config=Config(connect_timeout=1, read_timeout=1, retries={'max_attempts': 1}))
+dynamodbResource = boto3.resource('dynamodb', endpoint_url="http://localhost:8000",
+                                  region_name='local',
+                                  config=Config(connect_timeout=1, read_timeout=1, retries={'max_attempts': 1}))