peepstick 0.1.0__tar.gz

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,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: peepstick
3
+ Version: 0.1.0
4
+ Summary: Drop-in ticket form + Slack routing for Django apps, powered by PeepsTick
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: requests>=2.25
8
+ Provides-Extra: django
9
+ Requires-Dist: django>=4.0; extra == "django"
10
+ Provides-Extra: fastapi
11
+ Requires-Dist: fastapi>=0.100; extra == "fastapi"
12
+ Requires-Dist: pydantic>=2.0; extra == "fastapi"
13
+
14
+ # peepstick
15
+
16
+ Drop this into a Python app to get ticket routing that goes straight to
17
+ your team's Slack channel — configured entirely from the PeepsTick dashboard.
18
+
19
+ This package never talks to Slack. It only ever calls your PeepsTick
20
+ project's API (`/api/form-schema`, `/api/tickets`). PeepsTick owns the
21
+ Slack webhook and does the actual posting server-side.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ # FastAPI apps
27
+ pip install "peepstick[fastapi]"
28
+
29
+ # Django apps
30
+ pip install "peepstick[django]"
31
+ ```
32
+
33
+ ## FastAPI setup
34
+
35
+ ```python
36
+ from fastapi import FastAPI
37
+ from peepstick.fastapi import create_ticket_router
38
+
39
+ app = FastAPI()
40
+ app.include_router(
41
+ create_ticket_router(
42
+ api_key="tk_...", # from your project's page in the PeepsTick dashboard
43
+ base_url="https://peepstick.yourcompany.com",
44
+ ),
45
+ prefix="/peepstick",
46
+ )
47
+ ```
48
+
49
+ This gives your app two routes your frontend can call — the API key
50
+ never reaches the browser:
51
+
52
+ - `GET /peepstick/form-schema` — the live fields configured for your project
53
+ - `POST /peepstick/tickets` — submits `{"fields": {...}}`, validated and
54
+ routed to Slack server-side
55
+
56
+ Add or rename fields in the PeepsTick dashboard and `/peepstick/form-schema`
57
+ updates automatically — no code changes or redeploys needed.
58
+
59
+ ## Django setup
60
+
61
+ 1. In `settings.py`, add the app and your project's credentials
62
+ (find the API key on your project's page in the PeepsTick dashboard):
63
+
64
+ ```python
65
+ INSTALLED_APPS = [
66
+ ...,
67
+ "peepstick.django",
68
+ ]
69
+
70
+ PEEPSTICK_API_KEY = "tk_..."
71
+ PEEPSTICK_BASE_URL = "https://peepstick.yourcompany.com"
72
+ ```
73
+
74
+ 2. In your project's `urls.py`:
75
+
76
+ ```python
77
+ from django.urls import include, path
78
+
79
+ urlpatterns = [
80
+ ...,
81
+ path("tickets/", include("peepstick.django.urls")),
82
+ ]
83
+ ```
84
+
85
+ 3. Visit `/tickets/`. The form renders whatever fields are configured
86
+ for your project in the PeepsTick dashboard — add or rename fields
87
+ there and the form updates automatically, no code changes needed.
88
+
89
+ ## Using the client directly
90
+
91
+ If you'd rather build your own view or call PeepsTick from a script:
92
+
93
+ ```python
94
+ from peepstick import PeepsTickClient
95
+
96
+ client = PeepsTickClient(api_key="tk_...", base_url="https://peepstick.yourcompany.com")
97
+
98
+ fields = client.get_form_schema()
99
+ ticket = client.submit_ticket({"title": "Login broken", "priority": "High"})
100
+ ```
101
+
102
+ Both methods raise `peepstick.PeepsTickError` on failure (network
103
+ issues, invalid API key, missing required fields, etc).
@@ -0,0 +1,90 @@
1
+ # peepstick
2
+
3
+ Drop this into a Python app to get ticket routing that goes straight to
4
+ your team's Slack channel — configured entirely from the PeepsTick dashboard.
5
+
6
+ This package never talks to Slack. It only ever calls your PeepsTick
7
+ project's API (`/api/form-schema`, `/api/tickets`). PeepsTick owns the
8
+ Slack webhook and does the actual posting server-side.
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ # FastAPI apps
14
+ pip install "peepstick[fastapi]"
15
+
16
+ # Django apps
17
+ pip install "peepstick[django]"
18
+ ```
19
+
20
+ ## FastAPI setup
21
+
22
+ ```python
23
+ from fastapi import FastAPI
24
+ from peepstick.fastapi import create_ticket_router
25
+
26
+ app = FastAPI()
27
+ app.include_router(
28
+ create_ticket_router(
29
+ api_key="tk_...", # from your project's page in the PeepsTick dashboard
30
+ base_url="https://peepstick.yourcompany.com",
31
+ ),
32
+ prefix="/peepstick",
33
+ )
34
+ ```
35
+
36
+ This gives your app two routes your frontend can call — the API key
37
+ never reaches the browser:
38
+
39
+ - `GET /peepstick/form-schema` — the live fields configured for your project
40
+ - `POST /peepstick/tickets` — submits `{"fields": {...}}`, validated and
41
+ routed to Slack server-side
42
+
43
+ Add or rename fields in the PeepsTick dashboard and `/peepstick/form-schema`
44
+ updates automatically — no code changes or redeploys needed.
45
+
46
+ ## Django setup
47
+
48
+ 1. In `settings.py`, add the app and your project's credentials
49
+ (find the API key on your project's page in the PeepsTick dashboard):
50
+
51
+ ```python
52
+ INSTALLED_APPS = [
53
+ ...,
54
+ "peepstick.django",
55
+ ]
56
+
57
+ PEEPSTICK_API_KEY = "tk_..."
58
+ PEEPSTICK_BASE_URL = "https://peepstick.yourcompany.com"
59
+ ```
60
+
61
+ 2. In your project's `urls.py`:
62
+
63
+ ```python
64
+ from django.urls import include, path
65
+
66
+ urlpatterns = [
67
+ ...,
68
+ path("tickets/", include("peepstick.django.urls")),
69
+ ]
70
+ ```
71
+
72
+ 3. Visit `/tickets/`. The form renders whatever fields are configured
73
+ for your project in the PeepsTick dashboard — add or rename fields
74
+ there and the form updates automatically, no code changes needed.
75
+
76
+ ## Using the client directly
77
+
78
+ If you'd rather build your own view or call PeepsTick from a script:
79
+
80
+ ```python
81
+ from peepstick import PeepsTickClient
82
+
83
+ client = PeepsTickClient(api_key="tk_...", base_url="https://peepstick.yourcompany.com")
84
+
85
+ fields = client.get_form_schema()
86
+ ticket = client.submit_ticket({"title": "Login broken", "priority": "High"})
87
+ ```
88
+
89
+ Both methods raise `peepstick.PeepsTickError` on failure (network
90
+ issues, invalid API key, missing required fields, etc).
@@ -0,0 +1,4 @@
1
+ from .client import PeepsTickClient, PeepsTickError
2
+
3
+ __all__ = ["PeepsTickClient", "PeepsTickError"]
4
+ __version__ = "0.1.0"
@@ -0,0 +1,60 @@
1
+ """Thin HTTP client for the PeepsTick ticket-routing API.
2
+
3
+ This is the only file in the package that talks to the network. It never
4
+ talks to Slack — it only ever calls your PeepsTick dashboard's API, which is
5
+ the piece that owns the Slack webhook and does the actual posting.
6
+ """
7
+ import requests
8
+
9
+ DEFAULT_TIMEOUT = 5
10
+
11
+
12
+ class PeepsTickError(Exception):
13
+ """Raised when the PeepsTick API returns an error or can't be reached."""
14
+
15
+
16
+ class PeepsTickClient:
17
+ """Talks to a single PeepsTick project, identified by its API key."""
18
+
19
+ def __init__(self, api_key: str, base_url: str, timeout: int = DEFAULT_TIMEOUT):
20
+ if not api_key:
21
+ raise ValueError("PeepsTick api_key is required")
22
+ if not base_url:
23
+ raise ValueError("PeepsTick base_url is required")
24
+ self.api_key = api_key
25
+ self.base_url = base_url.rstrip("/")
26
+ self.timeout = timeout
27
+
28
+ def get_form_schema(self):
29
+ """Fetches the live form schema configured for this project."""
30
+ url = f"{self.base_url}/api/form-schema"
31
+ resp = self._request("get", url)
32
+ return self._parse(resp).get("formSchema", [])
33
+
34
+ def submit_ticket(self, fields: dict):
35
+ """Submits ticket field values. PeepsTick validates, stores, and
36
+ relays to Slack server-side — this call never sees the webhook."""
37
+ url = f"{self.base_url}/api/tickets"
38
+ resp = self._request("post", url, json={"fields": fields})
39
+ return self._parse(resp).get("ticket")
40
+
41
+ def _request(self, method: str, url: str, **kwargs):
42
+ try:
43
+ return requests.request(
44
+ method, url, headers=self._headers(), timeout=self.timeout, **kwargs
45
+ )
46
+ except requests.RequestException as exc:
47
+ raise PeepsTickError(f"Could not reach PeepsTick at {url}: {exc}") from exc
48
+
49
+ def _headers(self):
50
+ return {"x-api-key": self.api_key, "Content-Type": "application/json"}
51
+
52
+ @staticmethod
53
+ def _parse(resp):
54
+ try:
55
+ data = resp.json()
56
+ except ValueError:
57
+ data = {}
58
+ if resp.status_code >= 400:
59
+ raise PeepsTickError(data.get("error", f"PeepsTick API error ({resp.status_code})"))
60
+ return data
@@ -0,0 +1 @@
1
+ default_app_config = "peepstick.django.apps.PeepsTickConfig"
@@ -0,0 +1,8 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class PeepsTickConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "peepstick.django"
7
+ label = "peepstick_django"
8
+ verbose_name = "PeepsTick Tickets"
@@ -0,0 +1,8 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head><meta charset="utf-8"><title>Ticket form unavailable</title></head>
4
+ <body>
5
+ <h1>Ticket form unavailable</h1>
6
+ <p>{{ error }}</p>
7
+ </body>
8
+ </html>
@@ -0,0 +1,36 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head><meta charset="utf-8"><title>Submit a ticket</title></head>
4
+ <body>
5
+ <h1>Submit a ticket</h1>
6
+ <form method="post" action="{% url 'peepstick:submit_ticket' %}">
7
+ {% csrf_token %}
8
+ {% for field in fields %}
9
+ <div class="peepstick-field">
10
+ <label for="{{ field.id }}">
11
+ {{ field.label }}{% if field.required %} *{% endif %}
12
+ </label>
13
+
14
+ {% if field.type == "textarea" %}
15
+ <textarea id="{{ field.id }}" name="{{ field.id }}"
16
+ {% if field.required %}required{% endif %}></textarea>
17
+
18
+ {% elif field.options %}
19
+ <select id="{{ field.id }}" name="{{ field.id }}"
20
+ {% if field.required %}required{% endif %}>
21
+ <option value="">Select…</option>
22
+ {% for option in field.options %}
23
+ <option value="{{ option }}">{{ option }}</option>
24
+ {% endfor %}
25
+ </select>
26
+
27
+ {% else %}
28
+ <input type="text" id="{{ field.id }}" name="{{ field.id }}"
29
+ {% if field.required %}required{% endif %}>
30
+ {% endif %}
31
+ </div>
32
+ {% endfor %}
33
+ <button type="submit">Submit</button>
34
+ </form>
35
+ </body>
36
+ </html>
@@ -0,0 +1,10 @@
1
+ from django.urls import path
2
+
3
+ from . import views
4
+
5
+ app_name = "peepstick"
6
+
7
+ urlpatterns = [
8
+ path("", views.ticket_form, name="ticket_form"),
9
+ path("submit/", views.submit_ticket, name="submit_ticket"),
10
+ ]
@@ -0,0 +1,60 @@
1
+ import json
2
+
3
+ from django.conf import settings
4
+ from django.http import JsonResponse
5
+ from django.shortcuts import render
6
+ from django.views.decorators.csrf import csrf_exempt
7
+ from django.views.decorators.http import require_http_methods
8
+
9
+ from ..client import PeepsTickClient, PeepsTickError
10
+
11
+
12
+ def _client():
13
+ """Builds a PeepsTickClient from Django settings.
14
+
15
+ Add to settings.py:
16
+ PEEPSTICK_API_KEY = "tk_..." # from your PeepsTick project's dashboard
17
+ PEEPSTICK_BASE_URL = "https://peepstick.yourcompany.com"
18
+ """
19
+ api_key = getattr(settings, "PEEPSTICK_API_KEY", None)
20
+ base_url = getattr(settings, "PEEPSTICK_BASE_URL", None)
21
+ if not api_key or not base_url:
22
+ raise PeepsTickError(
23
+ "PEEPSTICK_API_KEY and PEEPSTICK_BASE_URL must be set in Django settings"
24
+ )
25
+ return PeepsTickClient(api_key=api_key, base_url=base_url)
26
+
27
+
28
+ def ticket_form(request):
29
+ """Renders an HTML ticket form built from the project's live schema.
30
+
31
+ The schema (which fields exist, which are required) lives in the
32
+ PeepsTick dashboard, not in this package — this view just fetches
33
+ whatever the project owner configured and renders it.
34
+ """
35
+ try:
36
+ fields = _client().get_form_schema()
37
+ except PeepsTickError as exc:
38
+ return render(request, "peepstick/error.html", {"error": str(exc)}, status=502)
39
+ return render(request, "peepstick/ticket_form.html", {"fields": fields})
40
+
41
+
42
+ @csrf_exempt
43
+ @require_http_methods(["POST"])
44
+ def submit_ticket(request):
45
+ """Accepts a ticket submission (form-encoded or JSON) and relays it
46
+ to PeepsTick's /api/tickets endpoint. PeepsTick validates it against the
47
+ project's schema, stores it, and posts to Slack server-side."""
48
+ if request.content_type == "application/json":
49
+ try:
50
+ fields = json.loads(request.body or "{}")
51
+ except json.JSONDecodeError:
52
+ return JsonResponse({"error": "Invalid JSON body"}, status=400)
53
+ else:
54
+ fields = request.POST.dict()
55
+
56
+ try:
57
+ ticket = _client().submit_ticket(fields)
58
+ except PeepsTickError as exc:
59
+ return JsonResponse({"error": str(exc)}, status=502)
60
+ return JsonResponse({"ticket": ticket}, status=201)
@@ -0,0 +1,58 @@
1
+ """FastAPI integration for PeepsTick.
2
+
3
+ Mounts two routes on your own app so the browser only ever talks to your
4
+ server, never to PeepsTick directly — the API key stays server-side.
5
+
6
+ from fastapi import FastAPI
7
+ from peepstick.fastapi import create_ticket_router
8
+
9
+ app = FastAPI()
10
+ app.include_router(
11
+ create_ticket_router(api_key="tk_...", base_url="https://peepstick.yourcompany.com"),
12
+ prefix="/peepstick",
13
+ )
14
+
15
+ This gives your app:
16
+ GET /peepstick/form-schema — proxies PeepsTick's live form schema
17
+ POST /peepstick/tickets — proxies a submission to PeepsTick
18
+
19
+ Requires FastAPI to be installed in your app already; it's not a
20
+ dependency of this package so you're never forced onto a version you
21
+ didn't choose.
22
+ """
23
+ from typing import Any, Dict
24
+
25
+ from .client import PeepsTickClient, PeepsTickError
26
+
27
+
28
+ def create_ticket_router(api_key: str, base_url: str, timeout: int = 5):
29
+ try:
30
+ from fastapi import APIRouter, HTTPException
31
+ from pydantic import BaseModel
32
+ except ImportError as exc:
33
+ raise ImportError(
34
+ "peepstick.fastapi requires fastapi and pydantic to be installed "
35
+ "in your app (pip install fastapi)"
36
+ ) from exc
37
+
38
+ client = PeepsTickClient(api_key=api_key, base_url=base_url, timeout=timeout)
39
+ router = APIRouter()
40
+
41
+ class TicketSubmission(BaseModel):
42
+ fields: Dict[str, Any]
43
+
44
+ @router.get("/form-schema")
45
+ def get_form_schema():
46
+ try:
47
+ return {"formSchema": client.get_form_schema()}
48
+ except PeepsTickError as exc:
49
+ raise HTTPException(status_code=502, detail=str(exc)) from exc
50
+
51
+ @router.post("/tickets", status_code=201)
52
+ def submit_ticket(submission: TicketSubmission):
53
+ try:
54
+ return {"ticket": client.submit_ticket(submission.fields)}
55
+ except PeepsTickError as exc:
56
+ raise HTTPException(status_code=502, detail=str(exc)) from exc
57
+
58
+ return router
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: peepstick
3
+ Version: 0.1.0
4
+ Summary: Drop-in ticket form + Slack routing for Django apps, powered by PeepsTick
5
+ Requires-Python: >=3.8
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: requests>=2.25
8
+ Provides-Extra: django
9
+ Requires-Dist: django>=4.0; extra == "django"
10
+ Provides-Extra: fastapi
11
+ Requires-Dist: fastapi>=0.100; extra == "fastapi"
12
+ Requires-Dist: pydantic>=2.0; extra == "fastapi"
13
+
14
+ # peepstick
15
+
16
+ Drop this into a Python app to get ticket routing that goes straight to
17
+ your team's Slack channel — configured entirely from the PeepsTick dashboard.
18
+
19
+ This package never talks to Slack. It only ever calls your PeepsTick
20
+ project's API (`/api/form-schema`, `/api/tickets`). PeepsTick owns the
21
+ Slack webhook and does the actual posting server-side.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ # FastAPI apps
27
+ pip install "peepstick[fastapi]"
28
+
29
+ # Django apps
30
+ pip install "peepstick[django]"
31
+ ```
32
+
33
+ ## FastAPI setup
34
+
35
+ ```python
36
+ from fastapi import FastAPI
37
+ from peepstick.fastapi import create_ticket_router
38
+
39
+ app = FastAPI()
40
+ app.include_router(
41
+ create_ticket_router(
42
+ api_key="tk_...", # from your project's page in the PeepsTick dashboard
43
+ base_url="https://peepstick.yourcompany.com",
44
+ ),
45
+ prefix="/peepstick",
46
+ )
47
+ ```
48
+
49
+ This gives your app two routes your frontend can call — the API key
50
+ never reaches the browser:
51
+
52
+ - `GET /peepstick/form-schema` — the live fields configured for your project
53
+ - `POST /peepstick/tickets` — submits `{"fields": {...}}`, validated and
54
+ routed to Slack server-side
55
+
56
+ Add or rename fields in the PeepsTick dashboard and `/peepstick/form-schema`
57
+ updates automatically — no code changes or redeploys needed.
58
+
59
+ ## Django setup
60
+
61
+ 1. In `settings.py`, add the app and your project's credentials
62
+ (find the API key on your project's page in the PeepsTick dashboard):
63
+
64
+ ```python
65
+ INSTALLED_APPS = [
66
+ ...,
67
+ "peepstick.django",
68
+ ]
69
+
70
+ PEEPSTICK_API_KEY = "tk_..."
71
+ PEEPSTICK_BASE_URL = "https://peepstick.yourcompany.com"
72
+ ```
73
+
74
+ 2. In your project's `urls.py`:
75
+
76
+ ```python
77
+ from django.urls import include, path
78
+
79
+ urlpatterns = [
80
+ ...,
81
+ path("tickets/", include("peepstick.django.urls")),
82
+ ]
83
+ ```
84
+
85
+ 3. Visit `/tickets/`. The form renders whatever fields are configured
86
+ for your project in the PeepsTick dashboard — add or rename fields
87
+ there and the form updates automatically, no code changes needed.
88
+
89
+ ## Using the client directly
90
+
91
+ If you'd rather build your own view or call PeepsTick from a script:
92
+
93
+ ```python
94
+ from peepstick import PeepsTickClient
95
+
96
+ client = PeepsTickClient(api_key="tk_...", base_url="https://peepstick.yourcompany.com")
97
+
98
+ fields = client.get_form_schema()
99
+ ticket = client.submit_ticket({"title": "Login broken", "priority": "High"})
100
+ ```
101
+
102
+ Both methods raise `peepstick.PeepsTickError` on failure (network
103
+ issues, invalid API key, missing required fields, etc).
@@ -0,0 +1,16 @@
1
+ README.md
2
+ pyproject.toml
3
+ peepstick/__init__.py
4
+ peepstick/client.py
5
+ peepstick/fastapi.py
6
+ peepstick.egg-info/PKG-INFO
7
+ peepstick.egg-info/SOURCES.txt
8
+ peepstick.egg-info/dependency_links.txt
9
+ peepstick.egg-info/requires.txt
10
+ peepstick.egg-info/top_level.txt
11
+ peepstick/django/__init__.py
12
+ peepstick/django/apps.py
13
+ peepstick/django/urls.py
14
+ peepstick/django/views.py
15
+ peepstick/django/templates/peepstick/error.html
16
+ peepstick/django/templates/peepstick/ticket_form.html
@@ -0,0 +1,8 @@
1
+ requests>=2.25
2
+
3
+ [django]
4
+ django>=4.0
5
+
6
+ [fastapi]
7
+ fastapi>=0.100
8
+ pydantic>=2.0
@@ -0,0 +1 @@
1
+ peepstick
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "peepstick"
7
+ version = "0.1.0"
8
+ description = "Drop-in ticket form + Slack routing for Django apps, powered by PeepsTick"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ dependencies = ["requests>=2.25"]
12
+
13
+ [project.optional-dependencies]
14
+ django = ["django>=4.0"]
15
+ fastapi = ["fastapi>=0.100", "pydantic>=2.0"]
16
+
17
+ [tool.setuptools.packages.find]
18
+ include = ["peepstick*"]
19
+
20
+ [tool.setuptools.package-data]
21
+ "peepstick.django" = ["templates/peepstick/*.html"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+