festivalapi 0.2.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.
@@ -0,0 +1,32 @@
1
+ """Festival API Python Client
2
+
3
+ A lightweight Python client for the Festival API.
4
+
5
+ pip install festivalapi
6
+
7
+ Usage:
8
+ from festivalapi import FestivalAPI
9
+
10
+ client = FestivalAPI("fda_your_api_key")
11
+
12
+ # Search festivals
13
+ festivals = client.festivals.list(category="short_film")
14
+
15
+ # Get festival detail
16
+ festival = client.festivals.get(1)
17
+
18
+ # Get festival roster
19
+ roster = client.festivals.roster(1)
20
+
21
+ # Get scored festivals
22
+ scored = client.festivals.scored(min_score=70)
23
+
24
+ # Health check (no auth)
25
+ health = client.health()
26
+ """
27
+
28
+ from .client import FestivalAPI
29
+ from .exceptions import FestivalAPIError, AuthenticationError, InsufficientCreditsError
30
+
31
+ __all__ = ["FestivalAPI", "FestivalAPIError", "AuthenticationError", "InsufficientCreditsError"]
32
+ __version__ = "0.2.0"
festivalapi/client.py ADDED
@@ -0,0 +1,165 @@
1
+ import urllib.request
2
+ import urllib.error
3
+ import urllib.parse
4
+ import json
5
+ import os
6
+ from typing import Optional, Dict, Any, List
7
+ from .exceptions import (
8
+ AuthenticationError,
9
+ InsufficientCreditsError,
10
+ NotFoundError,
11
+ RateLimitError,
12
+ ServerError,
13
+ FestivalAPIError,
14
+ )
15
+
16
+
17
+ class FestivalAPI:
18
+ """Client for the Festival API.
19
+
20
+ Args:
21
+ api_key: Your Festival API key (starts with 'fda_').
22
+ base_url: Override the API base URL. Defaults to production.
23
+ timeout: Request timeout in seconds. Default 30.
24
+ """
25
+
26
+ BASE_URL = "https://festivalapi.com/v1"
27
+
28
+ def __init__(
29
+ self,
30
+ api_key: Optional[str] = None,
31
+ base_url: Optional[str] = None,
32
+ timeout: int = 30,
33
+ ):
34
+ self.api_key = api_key or os.environ.get("FESTIVALAPI_KEY")
35
+ self.base_url = base_url or self.BASE_URL
36
+ self.timeout = timeout
37
+ self.festivals = FestivalEndpoints(self)
38
+ self.health = lambda: self._get("/health", auth_required=False)
39
+ self.categories = lambda: self._get("/categories", auth_required=False)
40
+
41
+ def _auth_headers(self) -> Dict[str, str]:
42
+ return {"Authorization": f"Bearer {self.api_key}"}
43
+
44
+ def _request(
45
+ self,
46
+ method: str,
47
+ path: str,
48
+ params: Optional[Dict[str, Any]] = None,
49
+ auth_required: bool = True,
50
+ ) -> Dict[str, Any]:
51
+ url = f"{self.base_url}{path}"
52
+ headers = {"Accept": "application/json", "User-Agent": "festivalapi-python/0.1.0"}
53
+ if auth_required:
54
+ if not self.api_key:
55
+ raise AuthenticationError(
56
+ "API key required. Pass api_key='fda_...' to FestivalAPI(), "
57
+ "or set FESTIVALAPI_KEY environment variable."
58
+ )
59
+ headers.update(self._auth_headers())
60
+
61
+ if params:
62
+ filtered = {k: v for k, v in params.items() if v is not None}
63
+ if filtered:
64
+ url = f"{url}?{urllib.parse.urlencode(filtered)}"
65
+
66
+ req = urllib.request.Request(url, method=method, headers=headers)
67
+
68
+ try:
69
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp:
70
+ return json.loads(resp.read().decode("utf-8"))
71
+ except urllib.error.HTTPError as e:
72
+ body = {}
73
+ try:
74
+ body = json.loads(e.read().decode("utf-8"))
75
+ except Exception:
76
+ pass
77
+ self._raise_error(e.code, body)
78
+ return {} # unreachable, satisfies type checker
79
+ except urllib.error.URLError as e:
80
+ raise FestivalAPIError(f"Connection error: {e.reason}") from e
81
+
82
+ def _get(
83
+ self, path: str, params=None, auth_required=True
84
+ ) -> Dict[str, Any]:
85
+ return self._request("GET", path, params, auth_required)
86
+
87
+ @staticmethod
88
+ def _raise_error(status: int, body: dict):
89
+ detail = body.get("detail") or body.get("message", "")
90
+ if status == 401:
91
+ raise AuthenticationError(detail or "Invalid or missing API key.")
92
+ if status == 402:
93
+ required = body.get("required", "?")
94
+ balance = body.get("balance", "?")
95
+ msg = f"Insufficient credits. Need {required}, have {balance}."
96
+ raise InsufficientCreditsError(msg)
97
+ if status == 404:
98
+ raise NotFoundError(detail or "Resource not found.")
99
+ if status == 429:
100
+ raise RateLimitError(detail or "Too many requests.")
101
+ if 500 <= status < 600:
102
+ raise ServerError(detail or f"Server error (HTTP {status}).")
103
+ raise FestivalAPIError(f"HTTP {status}: {detail or 'Unknown error'}")
104
+
105
+
106
+ class FestivalEndpoints:
107
+ """Access festival-related endpoints."""
108
+
109
+ def __init__(self, client: FestivalAPI):
110
+ self._client = client
111
+
112
+ def list(
113
+ self,
114
+ category: Optional[str] = None,
115
+ country: Optional[str] = None,
116
+ genre: Optional[str] = None,
117
+ deadline_before: Optional[str] = None,
118
+ fee_max: Optional[int] = None,
119
+ q: Optional[str] = None,
120
+ ) -> Dict[str, Any]:
121
+ """List festivals with optional filters.
122
+
123
+ Args:
124
+ category: Festival category (e.g. short_film, feature, documentary).
125
+ country: Country code (e.g. US, UK, CA).
126
+ genre: Accepted genre (e.g. drama, comedy, horror).
127
+ deadline_before: Filter by submission deadline before date (YYYY-MM-DD).
128
+ fee_max: Maximum submission fee in USD.
129
+ q: Full-text search on festival name.
130
+ """
131
+ return self._client._get("/festivals", {
132
+ "category": category,
133
+ "country": country,
134
+ "genre": genre,
135
+ "deadline_before": deadline_before,
136
+ "fee_max": fee_max,
137
+ "q": q,
138
+ })
139
+
140
+ def get(self, festival_id: int) -> Dict[str, Any]:
141
+ """Get full detail for a single festival."""
142
+ return self._client._get(f"/festivals/{festival_id}")
143
+
144
+ def roster(self, festival_id: int) -> Dict[str, Any]:
145
+ """Get the submission roster for a festival."""
146
+ return self._client._get(f"/festivals/{festival_id}/roster")
147
+
148
+ def scored(
149
+ self,
150
+ category: Optional[str] = None,
151
+ min_score: Optional[int] = None,
152
+ limit: Optional[int] = None,
153
+ ) -> Dict[str, Any]:
154
+ """Get festivals ranked by Festival Score.
155
+
156
+ Args:
157
+ category: Filter by category.
158
+ min_score: Minimum score threshold (0-100).
159
+ limit: Max results.
160
+ """
161
+ return self._client._get("/festivals/scored", {
162
+ "category": category,
163
+ "min_score": min_score,
164
+ "limit": limit,
165
+ })
@@ -0,0 +1,28 @@
1
+ class FestivalAPIError(Exception):
2
+ """Base exception for Festival API errors."""
3
+ pass
4
+
5
+
6
+ class AuthenticationError(FestivalAPIError):
7
+ """Invalid or missing API key (HTTP 401)."""
8
+ pass
9
+
10
+
11
+ class InsufficientCreditsError(FestivalAPIError):
12
+ """Not enough credits for the request (HTTP 402)."""
13
+ pass
14
+
15
+
16
+ class NotFoundError(FestivalAPIError):
17
+ """Resource not found (HTTP 404)."""
18
+ pass
19
+
20
+
21
+ class RateLimitError(FestivalAPIError):
22
+ """Rate limit exceeded (HTTP 429)."""
23
+ pass
24
+
25
+
26
+ class ServerError(FestivalAPIError):
27
+ """Server-side error (HTTP 5xx)."""
28
+ pass
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.4
2
+ Name: festivalapi
3
+ Version: 0.2.0
4
+ Summary: Python client for the Festival API — film festival data for developers
5
+ Author-email: Festival API <hello@festivalapi.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://festivalapi.com
8
+ Project-URL: Documentation, https://festivalapi.com/docs
9
+ Project-URL: Repository, https://github.com/rv888/festivalapi_com
10
+ Project-URL: Issues, https://github.com/rv888/festivalapi_com/issues
11
+ Keywords: film-festival,festival,submissions,film,api
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Internet :: WWW/HTTP
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+
24
+ # Festival API — Python Client
25
+
26
+ Client library for the [Festival API](https://festivalapi.com).
27
+
28
+ ```bash
29
+ pip install festivalapi
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ```python
35
+ from festivalapi import FestivalAPI
36
+
37
+ client = FestivalAPI("fda_your_api_key")
38
+
39
+ # Search festivals
40
+ festivals = client.festivals.list(category="short_film")
41
+
42
+ # Get festival detail
43
+ festival = client.festivals.get(1)
44
+
45
+ # Get festival roster
46
+ roster = client.festivals.roster(1)
47
+
48
+ # Get scored festivals
49
+ scored = client.festivals.scored(min_score=70)
50
+
51
+ # List categories (no auth required)
52
+ categories = client.categories()
53
+
54
+ # Health check (no auth)
55
+ client.health()
56
+ ```
57
+
58
+ ## API Key
59
+
60
+ Sign up at [festivalapi.com](https://festivalapi.com) to get your API key. You can also set the `FESTIVALAPI_KEY` environment variable.
61
+
62
+ ## Error Handling
63
+
64
+ ```python
65
+ from festivalapi import FestivalAPI, NotFoundError, InsufficientCreditsError
66
+
67
+ client = FestivalAPI("fda_your_api_key")
68
+
69
+ try:
70
+ festival = client.festivals.get(999999)
71
+ except NotFoundError:
72
+ print("Festival not found")
73
+ except InsufficientCreditsError as e:
74
+ print(f"Need more credits: {e}")
75
+ ```
76
+
77
+ ## Requirements
78
+
79
+ - Python 3.9+
80
+ - Zero dependencies (uses only stdlib `urllib`)
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,7 @@
1
+ festivalapi/__init__.py,sha256=PAI40RcrYTKGuM081yp8fz-MvQBUh6tlSr04DqqZoxo,805
2
+ festivalapi/client.py,sha256=LeFJ-p2wY8eXxC5zk0O3Ap7RjVYtRQJxPHwl0KbZiL0,5724
3
+ festivalapi/exceptions.py,sha256=YJFlQAsfxs4IdMVrj0hwifCmnP8-toi1Z8L6PeZTc8w,589
4
+ festivalapi-0.2.0.dist-info/METADATA,sha256=77afAFusAX7XVFYnTXo3wouLRB0mWlrrndAf59sMeKs,2194
5
+ festivalapi-0.2.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
6
+ festivalapi-0.2.0.dist-info/top_level.txt,sha256=kqEdtGIbJ43qJVla03SfVkMbM5ccJQdtwZKJwrRxWfk,12
7
+ festivalapi-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ festivalapi