fanduelapi 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fanduelapi/__init__.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Python client for FanDuel API (https://fanduelapi.com).
|
|
2
|
+
|
|
3
|
+
FanDuel API is an independent REST API for FanDuel Sportsbook data: player props, game lines,
|
|
4
|
+
alternates, futures, DFS salaries and the history of every line move. It is operated by
|
|
5
|
+
Stunning Couches LLC and is not affiliated with, endorsed by or sponsored by FanDuel.
|
|
6
|
+
|
|
7
|
+
No dependencies beyond the standard library.
|
|
8
|
+
|
|
9
|
+
from fanduelapi import FanDuelAPI
|
|
10
|
+
fd = FanDuelAPI() # reads FANDUEL_API_KEY
|
|
11
|
+
for p in fd.props(league="nba", stat="points", limit=20):
|
|
12
|
+
print(p["player"], p["line"], p["over_american"], p["under_american"])
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
import time
|
|
20
|
+
import urllib.error
|
|
21
|
+
import urllib.parse
|
|
22
|
+
import urllib.request
|
|
23
|
+
from typing import Any, Dict, Iterator, List, Optional
|
|
24
|
+
|
|
25
|
+
__all__ = ["FanDuelAPI", "FanDuelAPIError", "sample_props", "credit_costs", "__version__"]
|
|
26
|
+
__version__ = "0.1.0"
|
|
27
|
+
|
|
28
|
+
DEFAULT_BASE_URL = "https://fanduelapi.com"
|
|
29
|
+
USER_AGENT = f"fanduelapi-python/{__version__}"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FanDuelAPIError(Exception):
|
|
33
|
+
"""A non-2xx response. `status`, `code` and `body` carry the API's error object."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, status: int, code: str, message: str, body: Optional[dict] = None):
|
|
36
|
+
super().__init__(f"{status} {code}: {message}")
|
|
37
|
+
self.status = status
|
|
38
|
+
self.code = code
|
|
39
|
+
self.message = message
|
|
40
|
+
self.body = body or {}
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def upgrade_url(self) -> Optional[str]:
|
|
44
|
+
return (self.body.get("error") or {}).get("upgrade_url")
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def topup_url(self) -> Optional[str]:
|
|
48
|
+
return (self.body.get("error") or {}).get("topup_url")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _request(method: str, url: str, headers: Dict[str, str], body: Optional[dict], timeout: float):
|
|
52
|
+
data = json.dumps(body).encode() if body is not None else None
|
|
53
|
+
req = urllib.request.Request(url, data=data, method=method, headers=headers)
|
|
54
|
+
try:
|
|
55
|
+
with urllib.request.urlopen(req, timeout=timeout) as res:
|
|
56
|
+
raw = res.read().decode() or "{}"
|
|
57
|
+
return res.status, dict(res.headers), json.loads(raw)
|
|
58
|
+
except urllib.error.HTTPError as e:
|
|
59
|
+
raw = e.read().decode() or "{}"
|
|
60
|
+
try:
|
|
61
|
+
parsed = json.loads(raw)
|
|
62
|
+
except ValueError:
|
|
63
|
+
parsed = {"error": {"code": "HTTP_ERROR", "message": raw[:200]}}
|
|
64
|
+
return e.code, dict(e.headers), parsed
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class FanDuelAPI:
|
|
68
|
+
"""Client for the REST API at https://fanduelapi.com/api/v1.
|
|
69
|
+
|
|
70
|
+
Every method returns the response's `data` list (or object), except `request`, which returns
|
|
71
|
+
the whole body. On HTTP 429 the client waits for Retry-After and tries again, up to
|
|
72
|
+
`max_retries` times; a monthly quota stop (QUOTA_EXCEEDED) is raised at once, because waiting
|
|
73
|
+
does not help until the reset.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(self, api_key: Optional[str] = None, base_url: str = DEFAULT_BASE_URL, timeout: float = 30.0, max_retries: int = 3):
|
|
77
|
+
self.api_key = api_key if api_key is not None else os.environ.get("FANDUEL_API_KEY")
|
|
78
|
+
self.base_url = base_url.rstrip("/")
|
|
79
|
+
self.timeout = timeout
|
|
80
|
+
self.max_retries = max_retries
|
|
81
|
+
self.last_headers: Dict[str, str] = {}
|
|
82
|
+
|
|
83
|
+
# ---------------- transport ----------------
|
|
84
|
+
def request(self, method: str, path: str, params: Optional[Dict[str, Any]] = None, body: Optional[dict] = None) -> dict:
|
|
85
|
+
query = {k: v for k, v in (params or {}).items() if v is not None}
|
|
86
|
+
url = f"{self.base_url}{path}" + (f"?{urllib.parse.urlencode(query, doseq=True)}" if query else "")
|
|
87
|
+
headers = {"Accept": "application/json", "User-Agent": USER_AGENT}
|
|
88
|
+
if body is not None:
|
|
89
|
+
headers["Content-Type"] = "application/json"
|
|
90
|
+
if self.api_key:
|
|
91
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
92
|
+
attempt = 0
|
|
93
|
+
while True:
|
|
94
|
+
status, resp_headers, parsed = _request(method, url, headers, body, self.timeout)
|
|
95
|
+
self.last_headers = {k.lower(): v for k, v in resp_headers.items()}
|
|
96
|
+
if 200 <= status < 300:
|
|
97
|
+
return parsed
|
|
98
|
+
err = parsed.get("error") if isinstance(parsed, dict) else None
|
|
99
|
+
code = (err or {}).get("code", "HTTP_ERROR")
|
|
100
|
+
if status == 429 and code != "QUOTA_EXCEEDED" and attempt < self.max_retries:
|
|
101
|
+
attempt += 1
|
|
102
|
+
time.sleep(float(self.last_headers.get("retry-after", "5")))
|
|
103
|
+
continue
|
|
104
|
+
raise FanDuelAPIError(status, code, (err or {}).get("message", f"HTTP {status}"), parsed if isinstance(parsed, dict) else None)
|
|
105
|
+
|
|
106
|
+
def _get(self, path: str, **params: Any) -> Any:
|
|
107
|
+
return self.request("GET", f"/api/v1{path}", params).get("data")
|
|
108
|
+
|
|
109
|
+
# ---------------- data ----------------
|
|
110
|
+
def props(self, league: Optional[str] = None, stat: Optional[str] = None, player: Optional[str] = None, event_id: Optional[str] = None,
|
|
111
|
+
view: Optional[str] = None, updated_since: Optional[str] = None, limit: Optional[int] = None, offset: Optional[int] = None, **extra: Any) -> List[dict]:
|
|
112
|
+
"""Current FanDuel player props. view="compact" returns up to 1,000 rows in one call, without the move summary."""
|
|
113
|
+
return self._get("/props", league=league, stat=stat, player=player, event_id=event_id, view=view, updated_since=updated_since, limit=limit, offset=offset, **extra)
|
|
114
|
+
|
|
115
|
+
def prop_history(self, prop_id: str) -> Any:
|
|
116
|
+
"""Every recorded state of one prop: each line move, price move and status change."""
|
|
117
|
+
return self._get(f"/props/{urllib.parse.quote(prop_id, safe='')}/history")
|
|
118
|
+
|
|
119
|
+
def changes(self, since: Optional[str] = None, league: Optional[str] = None, limit: Optional[int] = None) -> dict:
|
|
120
|
+
"""One page of the change feed. Returns the whole body: `data` and `meta.next_cursor`."""
|
|
121
|
+
return self.request("GET", "/api/v1/changes", {"since": since, "league": league, "limit": limit})
|
|
122
|
+
|
|
123
|
+
def follow_changes(self, since: Optional[str] = None, league: Optional[str] = None, poll_seconds: Optional[float] = None) -> Iterator[dict]:
|
|
124
|
+
"""Yield every change forever, sleeping between empty pages for X-Next-Update-Seconds (or poll_seconds)."""
|
|
125
|
+
cursor = since
|
|
126
|
+
while True:
|
|
127
|
+
page = self.changes(since=cursor, league=league)
|
|
128
|
+
for change in page.get("data") or []:
|
|
129
|
+
yield change
|
|
130
|
+
cursor = (page.get("meta") or {}).get("next_cursor") or cursor
|
|
131
|
+
if not page.get("data"):
|
|
132
|
+
time.sleep(poll_seconds if poll_seconds is not None else float(self.last_headers.get("x-next-update-seconds", "30")))
|
|
133
|
+
|
|
134
|
+
def odds(self, league: Optional[str] = None, date: Optional[str] = None, market_type: Optional[str] = None, **extra: Any) -> Any:
|
|
135
|
+
"""The odds board: every game of a league with its game lines."""
|
|
136
|
+
return self._get("/odds", league=league, date=date, market_type=market_type, **extra)
|
|
137
|
+
|
|
138
|
+
def events(self, league: Optional[str] = None, **extra: Any) -> List[dict]:
|
|
139
|
+
return self._get("/events", league=league, **extra)
|
|
140
|
+
|
|
141
|
+
def markets(self, **params: Any) -> List[dict]:
|
|
142
|
+
return self._get("/markets", **params)
|
|
143
|
+
|
|
144
|
+
def game_report(self, event_id: str) -> Any:
|
|
145
|
+
return self._get(f"/games/{urllib.parse.quote(event_id, safe='')}/report")
|
|
146
|
+
|
|
147
|
+
def futures(self, league: Optional[str] = None, **extra: Any) -> List[dict]:
|
|
148
|
+
return self._get("/futures", league=league, **extra)
|
|
149
|
+
|
|
150
|
+
def compare(self, **params: Any) -> List[dict]:
|
|
151
|
+
"""FanDuel main line against its own alternate ladder, and against Novig, DraftKings and PrizePicks (view="books")."""
|
|
152
|
+
return self._get("/compare", **params)
|
|
153
|
+
|
|
154
|
+
def ev(self, **params: Any) -> List[dict]:
|
|
155
|
+
"""FanDuel prices above the Novig fair price, with EV, edge and Kelly. min_edge is in percent."""
|
|
156
|
+
return self._get("/ev", **params)
|
|
157
|
+
|
|
158
|
+
def arbitrage(self, **params: Any) -> List[dict]:
|
|
159
|
+
return self._get("/arbitrage", **params)
|
|
160
|
+
|
|
161
|
+
def results(self, league: Optional[str] = None, stat: Optional[str] = None, player: Optional[str] = None, **extra: Any) -> List[dict]:
|
|
162
|
+
"""Graded props: closing line, actual stat and result (paid plans)."""
|
|
163
|
+
return self._get("/results", league=league, stat=stat, player=player, **extra)
|
|
164
|
+
|
|
165
|
+
def hit_rates(self, player: str, stat: str, league: Optional[str] = None, games: Optional[int] = None) -> Any:
|
|
166
|
+
return self._get("/hit-rates", league=league, player=player, stat=stat, games=games)
|
|
167
|
+
|
|
168
|
+
def training_set(self, **params: Any) -> List[dict]:
|
|
169
|
+
return self._get("/training-set", **params)
|
|
170
|
+
|
|
171
|
+
def dfs_slates(self, sport: Optional[str] = None, **extra: Any) -> List[dict]:
|
|
172
|
+
return self._get("/dfs/slates", sport=sport, **extra)
|
|
173
|
+
|
|
174
|
+
def dfs_players(self, slate_id: str, **params: Any) -> List[dict]:
|
|
175
|
+
return self._get(f"/dfs/slates/{urllib.parse.quote(slate_id, safe='')}/players", **params)
|
|
176
|
+
|
|
177
|
+
def injuries(self, league: Optional[str] = None, **extra: Any) -> List[dict]:
|
|
178
|
+
return self._get("/injuries", league=league, **extra)
|
|
179
|
+
|
|
180
|
+
def coverage(self) -> Any:
|
|
181
|
+
return self._get("/coverage")
|
|
182
|
+
|
|
183
|
+
def credits_used(self) -> Dict[str, str]:
|
|
184
|
+
"""X-Credits-* headers of the last response."""
|
|
185
|
+
return {k: v for k, v in self.last_headers.items() if k.startswith("x-credits")}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def credit_costs(path: Optional[str] = None, base_url: str = DEFAULT_BASE_URL) -> Any:
|
|
189
|
+
"""The credit cost table, or with `path` the quote for that one call ({"path", "credits"}). No key needed."""
|
|
190
|
+
body = FanDuelAPI(api_key="", base_url=base_url).request("GET", "/api/v1/credit-costs", {"path": path})
|
|
191
|
+
return (body.get("meta") or {}).get("quote") if path else body.get("data")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def sample_props(sport: str = "nba", base_url: str = DEFAULT_BASE_URL, timeout: float = 30.0) -> List[dict]:
|
|
195
|
+
"""Up to 10 real FanDuel props, delayed like the Free plan, from the keyless MCP tool get_sample_props. No key needed."""
|
|
196
|
+
body = {"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "get_sample_props", "arguments": {"sport": sport}}}
|
|
197
|
+
headers = {"Content-Type": "application/json", "Accept": "application/json, text/event-stream", "User-Agent": USER_AGENT}
|
|
198
|
+
status, _, parsed = _request("POST", f"{base_url.rstrip('/')}/api/mcp", headers, body, timeout)
|
|
199
|
+
if status != 200 or "result" not in parsed:
|
|
200
|
+
raise FanDuelAPIError(status, "MCP_ERROR", json.dumps(parsed.get("error", parsed))[:200], parsed)
|
|
201
|
+
text = parsed["result"]["content"][0]["text"]
|
|
202
|
+
return json.loads(text).get("data", [])
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fanduelapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client for FanDuel API: FanDuel Sportsbook player props, odds and line movement history as JSON.
|
|
5
|
+
Project-URL: Repository, https://github.com/fanduelapi/fanduelapi-python
|
|
6
|
+
Project-URL: Issues, https://github.com/fanduelapi/fanduelapi-python/issues
|
|
7
|
+
Project-URL: Homepage, https://fanduelapi.com
|
|
8
|
+
Project-URL: Documentation, https://fanduelapi.com/docs
|
|
9
|
+
Project-URL: Pricing, https://fanduelapi.com/pricing
|
|
10
|
+
Author-email: Stunning Couches LLC <support@fanduelapi.com>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: fanduel,line movement,odds,odds api,player props,sports betting,sportsbook
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# fanduelapi
|
|
23
|
+
|
|
24
|
+
Python client for [FanDuel API](https://fanduelapi.com): FanDuel Sportsbook player props, game lines, alternates, futures, DFS salaries and the history of every line move, as JSON. Standard library only, Python 3.8+.
|
|
25
|
+
|
|
26
|
+
FanDuel API is an independent data service operated by Stunning Couches LLC. It is not affiliated with, endorsed by or sponsored by FanDuel.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install fanduelapi
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Try it with no key
|
|
35
|
+
|
|
36
|
+
Two helpers need no account: a small delayed sample of real props, and the credit cost of any call.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import fanduelapi
|
|
40
|
+
|
|
41
|
+
for p in fanduelapi.sample_props("nba")[:5]:
|
|
42
|
+
print(f"{p['player']:<22} {p['stat']:<9} {p['line']:>5} over {p['over_american']:>5} under {p['under_american']:>5}")
|
|
43
|
+
|
|
44
|
+
print(fanduelapi.credit_costs("/api/v1/props?league=nba&limit=200"))
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Output on 15 November 2026 (the sample is delayed like the Free plan, so yours will differ):
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Aaron Nesmith assists 1.5 over 108 under -144
|
|
51
|
+
Aaron Nesmith pa 14.5 over -114 under -114
|
|
52
|
+
Aaron Nesmith points 13.5 over 102 under -136
|
|
53
|
+
Aaron Nesmith pr 16.5 over -118 under -112
|
|
54
|
+
Aaron Nesmith pra 18.5 over -104 under -128
|
|
55
|
+
{'path': '/api/v1/props?league=nba&limit=200', 'credits': 6}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## With a key
|
|
59
|
+
|
|
60
|
+
Get a free key (no card) at https://fanduelapi.com/sign-up and export it:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
export FANDUEL_API_KEY="fd_..."
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from fanduelapi import FanDuelAPI
|
|
68
|
+
|
|
69
|
+
fd = FanDuelAPI() # reads FANDUEL_API_KEY
|
|
70
|
+
|
|
71
|
+
# Every NBA points prop, with where it opened and how often it moved
|
|
72
|
+
for p in fd.props(league="nba", stat="points", limit=50):
|
|
73
|
+
m = p["move"]
|
|
74
|
+
print(p["player"], p["line"], p["over_american"], p["under_american"],
|
|
75
|
+
"opened", m["open"]["line"], "line moves", m["line_moves"])
|
|
76
|
+
|
|
77
|
+
# The whole board in one call (compact rows, 1 credit per 100 rows)
|
|
78
|
+
board = fd.props(league="nfl", view="compact", limit=1000)
|
|
79
|
+
|
|
80
|
+
# Every recorded move of one prop
|
|
81
|
+
history = fd.prop_history(board[0]["id"])
|
|
82
|
+
|
|
83
|
+
# Game lines for every game of a league
|
|
84
|
+
games = fd.odds(league="nhl")
|
|
85
|
+
|
|
86
|
+
# FanDuel prices above the exchange fair price (min_edge is in percent)
|
|
87
|
+
value = fd.ev(league="nba", min_edge=2)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Follow line moves
|
|
91
|
+
|
|
92
|
+
`follow_changes` reads the change feed with a cursor and sleeps between empty pages for as long as the API's `X-Next-Update-Seconds` header says:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
for c in fd.follow_changes(league="NBA"):
|
|
96
|
+
if c["kind"] == "line_move":
|
|
97
|
+
print(c["player"], c["stat"], c["previous"]["line"], "->", c["current"]["line"])
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Errors and limits
|
|
101
|
+
|
|
102
|
+
Non-2xx responses raise `FanDuelAPIError` with `status`, `code` and the API's error body. Short rate limits (429 with `Retry-After`) are retried automatically. A monthly stop (`QUOTA_EXCEEDED`) is raised at once with `topup_url` and `upgrade_url` for the person to click; the client never buys anything.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from fanduelapi import FanDuelAPIError
|
|
106
|
+
try:
|
|
107
|
+
fd.results(league="nba")
|
|
108
|
+
except FanDuelAPIError as e:
|
|
109
|
+
print(e.code, e.topup_url)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`fd.credits_used()` returns the `X-Credits-*` headers of the last response.
|
|
113
|
+
|
|
114
|
+
## Methods
|
|
115
|
+
|
|
116
|
+
| Method | Endpoint |
|
|
117
|
+
| --- | --- |
|
|
118
|
+
| `props(league, stat, player, event_id, view, updated_since, limit, offset)` | `GET /api/v1/props` |
|
|
119
|
+
| `prop_history(prop_id)` | `GET /api/v1/props/{id}/history` |
|
|
120
|
+
| `changes(since, league, limit)`, `follow_changes(...)` | `GET /api/v1/changes` |
|
|
121
|
+
| `odds(league, date, market_type)` | `GET /api/v1/odds` |
|
|
122
|
+
| `events`, `markets`, `futures`, `game_report(event_id)` | events, markets, futures, game report |
|
|
123
|
+
| `compare`, `ev`, `arbitrage` | alternates and other venues, +EV, arbitrage |
|
|
124
|
+
| `results`, `hit_rates(player, stat)`, `training_set` | graded results and model data (paid plans) |
|
|
125
|
+
| `dfs_slates`, `dfs_players(slate_id)`, `injuries`, `coverage` | DFS salaries, injuries, what is tracked |
|
|
126
|
+
| `fanduelapi.sample_props(sport)`, `fanduelapi.credit_costs(path)` | keyless |
|
|
127
|
+
|
|
128
|
+
Plans, credits and limits: https://fanduelapi.com/pricing and https://fanduelapi.com/credits. Full reference: https://fanduelapi.com/docs and https://fanduelapi.com/openapi.json.
|
|
129
|
+
|
|
130
|
+
## Tests
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
python -m unittest discover -s tests # offline, against a local fake server
|
|
134
|
+
FANDUEL_LIVE=1 python -m unittest discover -s tests # also hits the keyless live endpoints
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
fanduelapi/__init__.py,sha256=g2WaXw9vxG7PWP1yyj7lU2Kvyd9I_CtCKGsQXAtxkS4,10503
|
|
2
|
+
fanduelapi-0.1.0.dist-info/METADATA,sha256=iEIxNK3q2zeVJqN_EiiDnrndjHmmY3xXRAxAil_4qVk,5308
|
|
3
|
+
fanduelapi-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
4
|
+
fanduelapi-0.1.0.dist-info/licenses/LICENSE,sha256=fPaBah2bETIgfvPKPxdsnSYOM8etWW_uvYwTiwARegk,1077
|
|
5
|
+
fanduelapi-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stunning Couches LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
6
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
|
7
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
8
|
+
persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
|
11
|
+
Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
14
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
15
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
16
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|