Compare commits

...

6 Commits

Author SHA1 Message Date
ed8f93d7d0 Set required python version to 3.7 2023-04-23 11:51:06 +02:00
dfd582a439 Set required python version to 3.7 2023-04-23 11:48:11 +02:00
96adaac01a Update readme 2023-04-23 10:06:25 +02:00
fb6d1276e6 Better error handling 2023-04-23 10:03:50 +02:00
6a64709d17 Product methods for python SDK 2023-04-21 23:39:20 +02:00
270f742c95 Remove port parameter 2023-04-21 17:56:35 +02:00
8 changed files with 127 additions and 35 deletions

View File

@ -10,8 +10,7 @@ Check `demo.js` for usage example.
Edit `demo.js` and configure FairHopper Game host settings:
```javascript
const FAIRHOPPER_HOST = "http://127.0.0.1";
const FAIRHOPPER_PORT = 8010;
const FAIRHOPPER_HOST = "http://127.0.0.1:8010";
```
Run example using node:

View File

@ -1,12 +1,11 @@
const sdk = require("./fh_sdk");
const FAIRHOPPER_HOST = "http://127.0.0.1";
const FAIRHOPPER_PORT = 8010;
const FAIRHOPPER_HOST = "https://api.fairhopper.mjerenja.com";
async function main() {
const fh = new sdk.FairHopper(FAIRHOPPER_HOST, FAIRHOPPER_PORT);
const fh = new sdk.FairHopper(FAIRHOPPER_HOST);
console.log(`Pinging FairHopper server on ${FAIRHOPPER_HOST}:${FAIRHOPPER_PORT}`);
console.log(`Pinging FairHopper server on ${FAIRHOPPER_HOST}`);
const pingResult = await fh.ping();
console.log("Ping result:", pingResult);
console.log();

View File

@ -1,3 +1,13 @@
class ValidationError extends Error {
constructor(message) {
if (!message) {
message = "ValidationError";
}
super(message);
this.name = "ValidationError";
}
}
class PlayerNotFoundError extends Error {
constructor(message) {
if (!message) {
@ -36,9 +46,8 @@ const Direction = {
};
class FairHopper {
constructor(host, port) {
constructor(host) {
this.host = host;
this.port = port;
this.defaultHeaders = {
Accept: "application/json",
@ -47,7 +56,7 @@ class FairHopper {
}
formatUrl(path) {
return `${this.host}:${this.port}${path}`;
return `${this.host}${path}`;
}
async ping() {
@ -66,6 +75,10 @@ class FairHopper {
headers: this.defaultHeaders,
body: JSON.stringify(payload),
});
switch (r.status) {
case 422:
throw new ValidationError();
}
return await r.json();
}
@ -85,6 +98,8 @@ class FairHopper {
throw new PlayerInactiveError();
case 404:
throw new PlayerNotFoundError();
case 422:
throw new ValidationError();
}
return await r.json();
}
@ -118,6 +133,8 @@ class FairHopper {
throw new PlayerNotFoundError();
case 409:
throw new PositionError();
case 422:
throw new ValidationError();
}
return await r.json();
}
@ -129,6 +146,17 @@ class FairHopper {
return await r.json();
}
async getProduct(productId) {
const r = await fetch(this.formatUrl(`/products/${productId}`), {
headers: this.defaultHeaders,
});
switch (r.status) {
case 422:
throw new ValidationError();
}
return await r.json();
}
async purchaseProduct(playerId, productId) {
const url = this.formatUrl(`/player/${playerId}/product/purchase`);
const postData = {
@ -146,6 +174,8 @@ class FairHopper {
throw new PlayerNotFoundError();
case 409:
throw new PositionError();
case 422:
throw new ValidationError();
}
return await r.json();
}

View File

@ -1,7 +1,7 @@
# FairHopper Python SDK
Requirements:
- Python 3.10+
- Python 3.7+
## Setting up environment
@ -24,8 +24,7 @@ Check `demo.py` for usage example.
Edit `demo.py` and configure FairHopper Game host settings:
```python
FAIRHOPPER_HOST = "http://127.0.0.1"
FAIRHOPPER_PORT = 8010
FAIRHOPPER_HOST = "http://127.0.0.1:8010"
```
Activate environment and run example:

View File

@ -1,13 +1,12 @@
from fh_sdk import Direction, FairHopper, PositionError
FAIRHOPPER_HOST = "http://127.0.0.1"
FAIRHOPPER_PORT = 8010
FAIRHOPPER_HOST = "https://api.fairhopper.mjerenja.com"
def main() -> None:
fh = FairHopper(FAIRHOPPER_HOST, FAIRHOPPER_PORT)
fh = FairHopper(FAIRHOPPER_HOST)
print(f"Pinging FairHopper server on {FAIRHOPPER_HOST}:{FAIRHOPPER_PORT}")
print(f"Pinging FairHopper server on {FAIRHOPPER_HOST}")
res = fh.ping()
print("Ping result:", res)
print()

View File

@ -1,3 +1,4 @@
from typing import List, Optional
from pydantic import BaseModel
from enum import Enum
@ -20,6 +21,10 @@ class PlayerNotFoundError(BaseError):
...
class ValidationError(BaseError):
...
class Direction(str, Enum):
LEFT = "left"
RIGHT = "right"
@ -27,6 +32,13 @@ class Direction(str, Enum):
DOWN = "down"
class PlayerState(str, Enum):
CREATED = "CREATED"
MOVING = "MOVING"
ON_DESTINATION = "ON_DESTINATION"
INACTIVE = "INACTIVE"
class Board(BaseModel):
width: int
height: int
@ -48,6 +60,13 @@ class Player(BaseModel):
position: Position
move_count: int
move_attempt_count: int
state: PlayerState
class Product(BaseModel):
name: str
id: str
description: Optional[str] = None
class PingResponse(BaseModel):
@ -69,13 +88,16 @@ class GameInfoResponse(BaseModel):
destination: Destination
class ProductListResponse(BaseModel):
products: List[Product]
class FairHopper:
def __init__(self, host, port) -> None:
def __init__(self, host: str) -> None:
self.host = host
self.port = port
def format_url(self, path: str) -> str:
return f"{self.host}:{self.port}{path}"
return f"{self.host}{path}"
def ping(self) -> PingResponse:
r = requests.get(self.format_url("/ping"))
@ -87,7 +109,12 @@ class FairHopper:
"player_name": player_name,
}
r = requests.post(self.format_url("/game"), json=payload)
r.raise_for_status()
if r.status_code == 422:
raise ValidationError()
else:
r.raise_for_status()
return StartGameResponse(**r.json())
def get_game_info(self) -> GameInfoResponse:
@ -95,32 +122,34 @@ class FairHopper:
r.raise_for_status()
return GameInfoResponse(**r.json())
def get_player_info(self, id: str) -> PlayerInfoResponse:
r = requests.get(self.format_url(f"/player/{id}"))
def get_player_info(self, player_id: str) -> PlayerInfoResponse:
r = requests.get(self.format_url(f"/player/{player_id}"))
if r.status_code == 403:
raise PlayerInactiveError()
elif r.status_code == 404:
raise PlayerNotFoundError()
elif r.status_code == 422:
raise ValidationError()
else:
r.raise_for_status()
return PlayerInfoResponse(**r.json())
def move_left(self, id: str) -> PlayerInfoResponse:
return self.move(id, Direction.LEFT)
def move_left(self, player_id: str) -> PlayerInfoResponse:
return self.move(player_id, Direction.LEFT)
def move_right(self, id: str) -> PlayerInfoResponse:
return self.move(id, Direction.RIGHT)
def move_right(self, player_id: str) -> PlayerInfoResponse:
return self.move(player_id, Direction.RIGHT)
def move_up(self, id: str) -> PlayerInfoResponse:
return self.move(id, Direction.UP)
def move_up(self, player_id: str) -> PlayerInfoResponse:
return self.move(player_id, Direction.UP)
def move_down(self, id: str) -> PlayerInfoResponse:
return self.move(id, Direction.DOWN)
def move_down(self, player_id: str) -> PlayerInfoResponse:
return self.move(player_id, Direction.DOWN)
def move(self, id: str, direction: Direction) -> PlayerInfoResponse:
path = f"/player/{id}/move/{direction}"
def move(self, player_id: str, direction: Direction) -> PlayerInfoResponse:
path = f"/player/{player_id}/move/{direction}"
r = requests.post(self.format_url(path))
if r.status_code == 403:
@ -129,7 +158,44 @@ class FairHopper:
raise PlayerNotFoundError()
elif r.status_code == 409:
raise PositionError()
elif r.status_code == 422:
raise ValidationError()
else:
r.raise_for_status()
return PlayerInfoResponse(**r.json())
def get_products(self) -> List[Product]:
r = requests.get(self.format_url("/products"))
response_data = ProductListResponse(**r.json())
return response_data.products
def get_product(self, product_id: str) -> Product:
r = requests.get(self.format_url(f"/products/{product_id}"))
if r.status_code == 422:
raise ValidationError()
else:
r.raise_for_status()
return Product(**r.json())
def purchase_product(self, player_id: str, product_id: str) -> Product:
url = self.format_url(f"/player/{player_id}/product/purchase")
payload = {
"product_id": product_id,
}
r = requests.post(url, json=payload)
if r.status_code == 403:
raise PlayerInactiveError()
elif r.status_code == 404:
raise PlayerNotFoundError()
elif r.status_code == 409:
raise PositionError()
elif r.status_code == 422:
raise ValidationError()
else:
r.raise_for_status()
return Product(**r.json())

4
python/poetry.lock generated
View File

@ -215,5 +215,5 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "a8d95adf4819c22b47d4cac44f18a3bc5040d1c0487ead04cb2f09692de0d411"
python-versions = "^3.7"
content-hash = "6d7fc4c2ae40fa75f772d15817348dd97078b9aa25ecd3ad52e71bec25732be8"

View File

@ -7,7 +7,7 @@ readme = "README.md"
packages = [{include = "fh_sdk"}]
[tool.poetry.dependencies]
python = "^3.10"
python = "^3.7"
requests = "^2.28.2"
pydantic = "^1.10.7"