letit 0.0.1__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.
letit-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 LetIt https://www.letit.com support@letit.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
letit-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: letit
3
+ Version: 0.0.1
4
+ Summary: Python SDK for the LetIt API
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: requests>=2.32.0
9
+ Dynamic: license-file
10
+
11
+ # LetIt Python
12
+
13
+ The official Python SDK for the [LetIt](https://letit.com) API.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install letit
19
+ ```
20
+
21
+ ## Setup
22
+
23
+ Find your API token at [https://letit.com/settings/developer](https://letit.com/settings/developer) after creating an account at [https://letit.com/register](https://letit.com/register).
24
+
25
+ ```python
26
+ from letit import LetIt
27
+
28
+ client = LetIt(api_token="your_api_token_here")
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ### Create a micropost
34
+
35
+ ```python
36
+ from letit.schemas.micropost import PostType
37
+
38
+ # Text post
39
+ post = client.micropost.client_create_micropost(
40
+ title="Hello World",
41
+ body="This is my first post.",
42
+ )
43
+ print(post.public_id)
44
+ print(post.link)
45
+
46
+ # Media post
47
+ with open("photo.png", "rb") as f:
48
+ post = client.micropost.client_create_micropost(
49
+ title="My photo",
50
+ body="Check this out",
51
+ post_type=PostType.MEDIA,
52
+ file=("photo.png", f, "image/png"),
53
+ )
54
+ ```
55
+
56
+ ### Create a job post
57
+
58
+ ```python
59
+ from letit.schemas.job import JobLocation, JobType, JobCategory, JobExperienceLevel
60
+
61
+ job = client.job.client_create_user_job_with_company(
62
+ company_name="LetIt",
63
+ company_description="We build things.",
64
+ company_website="https://letit.com",
65
+ job_title="Rust Engineer",
66
+ job_description="Build backend services in Rust.",
67
+ job_how_to_apply="https://letit.com/careers",
68
+ job_location=JobLocation.REMOTE,
69
+ job_type=JobType.FULLTIME,
70
+ job_category=JobCategory.PROGRAMMING,
71
+ job_experience_level=JobExperienceLevel.SENIOR,
72
+ job_skills="Rust, SQL",
73
+ )
74
+ print(job.slug)
75
+ ```
76
+
77
+ ## API Reference
78
+
79
+ ### `LetIt(api_token, base_url?)`
80
+
81
+ | Parameter | Type | Description |
82
+ |-----------|------|-------------|
83
+ | `api_token` | `str` | Your API token |
84
+ | `base_url` | `str` | API base URL (default: `https://api.letit.com`) |
85
+
86
+ ### `client.micropost.client_create_micropost(...)`
87
+
88
+ | Parameter | Type | Default | Description |
89
+ |-----------|------|---------|-------------|
90
+ | `body` | `str` | required | Content of the post |
91
+ | `title` | `str` | `None` | Required for original posts |
92
+ | `post_type` | `PostType` | `PostType.TEXT` | `PostType.TEXT` or `PostType.MEDIA` |
93
+ | `community_name` | `str` | `None` | Community to post in |
94
+ | `parent_micropost_public_id` | `str` | `None` | For replies |
95
+ | `parent_micropost_comment_public_id` | `str` | `None` | For nested replies |
96
+ | `allow_comments` | `bool` | `True` | Whether comments are allowed |
97
+ | `is_draft` | `bool` | `False` | Save as draft |
98
+ | `file` | `tuple` | `None` | `(filename, file_object, mime_type)` for MEDIA posts |
99
+
100
+ ### `client.job.client_create_user_job_with_company(...)`
101
+
102
+ | Parameter | Type | Default | Description |
103
+ |-----------|------|---------|-------------|
104
+ | `company_name` | `str` | required | Name of the company |
105
+ | `company_description` | `str` | required | Description of the company |
106
+ | `company_website` | `str` | required | Company website URL |
107
+ | `job_title` | `str` | required | Title of the job |
108
+ | `job_description` | `str` | required | Full job description |
109
+ | `job_how_to_apply` | `str` | required | URL or instructions to apply |
110
+ | `company_logo` | `tuple` | `None` | `(filename, file_object, mime_type)` |
111
+ | `company_location` | `str` | `None` | Company location |
112
+ | `job_location` | `JobLocation` | `JobLocation.REMOTE` | `REMOTE`, `ONSITE`, `HYBRID` |
113
+ | `job_type` | `JobType` | `JobType.FULLTIME` | `FULLTIME`, `PARTTIME`, `CONTRACT`, `FREELANCE`, `INTERNSHIP` |
114
+ | `job_category` | `JobCategory` | `JobCategory.PROGRAMMING` | `PROGRAMMING`, `BLOCKCHAIN`, `DESIGN`, `MARKETING`, `CUSTOMERSUPPORT`, `WRITING`, `PRODUCT`, `SERVICE`, `HUMANRESOURCE`, `ELSE` |
115
+ | `job_experience_level` | `JobExperienceLevel` | `JobExperienceLevel.ALL` | `ALL`, `JUNIOR`, `MID`, `SENIOR`, `NOEXPERIENCEREQUIRED` |
116
+ | `job_minimum_salary` | `int` | `None` | Minimum salary |
117
+ | `job_maximum_salary` | `int` | `None` | Maximum salary |
118
+ | `job_pay_in_cryptocurrency` | `bool` | `False` | Pay in cryptocurrency |
119
+ | `job_skills` | `str` | `None` | Comma-separated skills |
120
+
121
+ ## Development
122
+
123
+ ```bash
124
+ # Install in editable mode
125
+ pip install -e .
126
+
127
+ # Run unit tests
128
+ python -m pytest letit/tests/test_letit.py -v
129
+
130
+ # Run integration tests (requires real API token)
131
+ LETIT_API_TOKEN=your_token python -m pytest letit/tests/test_letit_integration.py -v -s
132
+ ```
133
+
134
+ ## License
135
+
136
+ MIT
letit-0.0.1/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # LetIt Python
2
+
3
+ The official Python SDK for the [LetIt](https://letit.com) API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install letit
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ Find your API token at [https://letit.com/settings/developer](https://letit.com/settings/developer) after creating an account at [https://letit.com/register](https://letit.com/register).
14
+
15
+ ```python
16
+ from letit import LetIt
17
+
18
+ client = LetIt(api_token="your_api_token_here")
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ### Create a micropost
24
+
25
+ ```python
26
+ from letit.schemas.micropost import PostType
27
+
28
+ # Text post
29
+ post = client.micropost.client_create_micropost(
30
+ title="Hello World",
31
+ body="This is my first post.",
32
+ )
33
+ print(post.public_id)
34
+ print(post.link)
35
+
36
+ # Media post
37
+ with open("photo.png", "rb") as f:
38
+ post = client.micropost.client_create_micropost(
39
+ title="My photo",
40
+ body="Check this out",
41
+ post_type=PostType.MEDIA,
42
+ file=("photo.png", f, "image/png"),
43
+ )
44
+ ```
45
+
46
+ ### Create a job post
47
+
48
+ ```python
49
+ from letit.schemas.job import JobLocation, JobType, JobCategory, JobExperienceLevel
50
+
51
+ job = client.job.client_create_user_job_with_company(
52
+ company_name="LetIt",
53
+ company_description="We build things.",
54
+ company_website="https://letit.com",
55
+ job_title="Rust Engineer",
56
+ job_description="Build backend services in Rust.",
57
+ job_how_to_apply="https://letit.com/careers",
58
+ job_location=JobLocation.REMOTE,
59
+ job_type=JobType.FULLTIME,
60
+ job_category=JobCategory.PROGRAMMING,
61
+ job_experience_level=JobExperienceLevel.SENIOR,
62
+ job_skills="Rust, SQL",
63
+ )
64
+ print(job.slug)
65
+ ```
66
+
67
+ ## API Reference
68
+
69
+ ### `LetIt(api_token, base_url?)`
70
+
71
+ | Parameter | Type | Description |
72
+ |-----------|------|-------------|
73
+ | `api_token` | `str` | Your API token |
74
+ | `base_url` | `str` | API base URL (default: `https://api.letit.com`) |
75
+
76
+ ### `client.micropost.client_create_micropost(...)`
77
+
78
+ | Parameter | Type | Default | Description |
79
+ |-----------|------|---------|-------------|
80
+ | `body` | `str` | required | Content of the post |
81
+ | `title` | `str` | `None` | Required for original posts |
82
+ | `post_type` | `PostType` | `PostType.TEXT` | `PostType.TEXT` or `PostType.MEDIA` |
83
+ | `community_name` | `str` | `None` | Community to post in |
84
+ | `parent_micropost_public_id` | `str` | `None` | For replies |
85
+ | `parent_micropost_comment_public_id` | `str` | `None` | For nested replies |
86
+ | `allow_comments` | `bool` | `True` | Whether comments are allowed |
87
+ | `is_draft` | `bool` | `False` | Save as draft |
88
+ | `file` | `tuple` | `None` | `(filename, file_object, mime_type)` for MEDIA posts |
89
+
90
+ ### `client.job.client_create_user_job_with_company(...)`
91
+
92
+ | Parameter | Type | Default | Description |
93
+ |-----------|------|---------|-------------|
94
+ | `company_name` | `str` | required | Name of the company |
95
+ | `company_description` | `str` | required | Description of the company |
96
+ | `company_website` | `str` | required | Company website URL |
97
+ | `job_title` | `str` | required | Title of the job |
98
+ | `job_description` | `str` | required | Full job description |
99
+ | `job_how_to_apply` | `str` | required | URL or instructions to apply |
100
+ | `company_logo` | `tuple` | `None` | `(filename, file_object, mime_type)` |
101
+ | `company_location` | `str` | `None` | Company location |
102
+ | `job_location` | `JobLocation` | `JobLocation.REMOTE` | `REMOTE`, `ONSITE`, `HYBRID` |
103
+ | `job_type` | `JobType` | `JobType.FULLTIME` | `FULLTIME`, `PARTTIME`, `CONTRACT`, `FREELANCE`, `INTERNSHIP` |
104
+ | `job_category` | `JobCategory` | `JobCategory.PROGRAMMING` | `PROGRAMMING`, `BLOCKCHAIN`, `DESIGN`, `MARKETING`, `CUSTOMERSUPPORT`, `WRITING`, `PRODUCT`, `SERVICE`, `HUMANRESOURCE`, `ELSE` |
105
+ | `job_experience_level` | `JobExperienceLevel` | `JobExperienceLevel.ALL` | `ALL`, `JUNIOR`, `MID`, `SENIOR`, `NOEXPERIENCEREQUIRED` |
106
+ | `job_minimum_salary` | `int` | `None` | Minimum salary |
107
+ | `job_maximum_salary` | `int` | `None` | Maximum salary |
108
+ | `job_pay_in_cryptocurrency` | `bool` | `False` | Pay in cryptocurrency |
109
+ | `job_skills` | `str` | `None` | Comma-separated skills |
110
+
111
+ ## Development
112
+
113
+ ```bash
114
+ # Install in editable mode
115
+ pip install -e .
116
+
117
+ # Run unit tests
118
+ python -m pytest letit/tests/test_letit.py -v
119
+
120
+ # Run integration tests (requires real API token)
121
+ LETIT_API_TOKEN=your_token python -m pytest letit/tests/test_letit_integration.py -v -s
122
+ ```
123
+
124
+ ## License
125
+
126
+ MIT
@@ -0,0 +1,3 @@
1
+ from .client import LetIt
2
+
3
+ __all__ = ["Letit"]
@@ -0,0 +1,39 @@
1
+ # $pip freeze > requirements.txt
2
+ # $pip install -r requirements.txt
3
+
4
+ from letit.settings import DEFAULT_API_SERVER
5
+ from letit.resources.micropost import MicropostResource
6
+ from letit.resources.job import JobResource
7
+ import requests
8
+
9
+ class LetIt:
10
+ """
11
+ Client for the LetIt API.
12
+
13
+ Args:
14
+ api_token: Your API token. Generate one at https://letit.com/settings/developer
15
+ base_url: Base URL for the API. Defaults to the standard LetIt API server.
16
+
17
+ Example:
18
+ client = LetIt(api_token="your_token_here")
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ api_token: str,
24
+ base_url: str = DEFAULT_API_SERVER,
25
+ ) -> None:
26
+ self.api_token = api_token
27
+ self.base_url = base_url
28
+ self._headers = {"USER-API-TOKEN": api_token}
29
+
30
+ self.micropost = MicropostResource(self)
31
+ self.job = JobResource(self)
32
+
33
+ def _request(self, method: str, path: str, **kwargs) -> requests.Response:
34
+ url = f"{self.base_url}{path}"
35
+ extra_headers = kwargs.pop("headers", {})
36
+ headers = {**self._headers, **extra_headers}
37
+ response = requests.request(method, url, headers=headers, **kwargs)
38
+ response.raise_for_status()
39
+ return response
File without changes
@@ -0,0 +1,118 @@
1
+ # letit/resources/job.py
2
+ from __future__ import annotations
3
+ from dataclasses import dataclass
4
+ from typing import TYPE_CHECKING, Optional
5
+ from requests_toolbelt.multipart.encoder import MultipartEncoder
6
+
7
+ # import sys
8
+ # import os
9
+ # sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
10
+
11
+ from letit.schemas.job import JobCategory, JobExperienceLevel, JobLocation, JobType, UserJobCreatedByUserResponse
12
+
13
+ if TYPE_CHECKING:
14
+ from ..client import Letit
15
+
16
+
17
+ @dataclass
18
+ class JobResponse:
19
+ slug: str
20
+
21
+ @classmethod
22
+ def from_dict(cls, data: dict) -> "JobResponse":
23
+ return cls(slug=data["slug"])
24
+
25
+
26
+ class JobResource:
27
+ def __init__(self, client: "Letit"):
28
+ self._client = client
29
+
30
+ def client_create_user_job_with_company(
31
+ self,
32
+ company_name: str,
33
+ company_description: str,
34
+ company_logo: tuple, # ("logo.png", open(..., "rb"), "image/png")
35
+ company_website: str,
36
+ job_title: str,
37
+ job_description: str,
38
+ job_how_to_apply: str,
39
+ company_location: Optional[str] = None,
40
+ job_location: JobLocation = JobLocation.REMOTE,
41
+ job_type: JobType = JobType.FULLTIME,
42
+ job_category: JobCategory = JobCategory.PROGRAMMING,
43
+ job_experience_level: JobExperienceLevel = JobExperienceLevel.ALL,
44
+ job_minimum_salary: Optional[int] = None,
45
+ job_maximum_salary: Optional[int] = None,
46
+ job_pay_in_cryptocurrency: bool = False,
47
+ job_skills: Optional[str] = None,
48
+ ) -> UserJobCreatedByUserResponse:
49
+ """
50
+ Create a job post with a company.
51
+
52
+ Args:
53
+ company_name: Name of the company.
54
+ company_description: Description of the company.
55
+ company_logo: Tuple of (filename, file_object, mime_type). Optional.
56
+ company_website: Company website URL.
57
+ job_title: Title of the job.
58
+ job_description: Full job description.
59
+ job_how_to_apply: URL or instructions to apply.
60
+ company_location: Optional company location.
61
+ job_location: Defaults to JobLocation.REMOTE.
62
+ job_type: Defaults to JobType.FULLTIME.
63
+ job_category: Defaults to JobCategory.PROGRAMMING.
64
+ job_experience_level: Defaults to JobExperienceLevel.ALL.
65
+ job_minimum_salary: Optional minimum salary.
66
+ job_maximum_salary: Optional maximum salary.
67
+ job_pay_in_cryptocurrency: Defaults to False.
68
+ job_skills: Comma-separated skills string.
69
+
70
+ Returns:
71
+ UserJobCreatedByUserResponse with slug.
72
+
73
+ Example:
74
+ job = client.job.client_create_user_job_with_company(
75
+ company_name="LetIt",
76
+ company_description="We build things.",
77
+ company_website="https://letit.com",
78
+ job_title="Rust Engineer",
79
+ job_description="Build backend services.",
80
+ job_how_to_apply="https://letit.com/careers",
81
+ job_skills="Rust, SQL, Docker",
82
+ )
83
+ """
84
+
85
+ fields = {
86
+ "company_name": company_name,
87
+ "company_description": company_description,
88
+ "company_website": company_website,
89
+ "job_title": job_title,
90
+ "job_description": job_description,
91
+ "job_how_to_apply": job_how_to_apply,
92
+ "job_location": job_location,
93
+ "job_type": job_type,
94
+ "job_category": job_category,
95
+ "job_experience_level": job_experience_level,
96
+ "job_pay_in_cryptocurrency": str(job_pay_in_cryptocurrency).lower(),
97
+ "company_logo": company_logo,
98
+ }
99
+
100
+ if company_location:
101
+ fields["company_location"] = company_location
102
+ if job_minimum_salary is not None:
103
+ fields["job_minimum_salary"] = str(job_minimum_salary)
104
+ if job_maximum_salary is not None:
105
+ fields["job_maximum_salary"] = str(job_maximum_salary)
106
+ if job_skills:
107
+ fields["job_skills"] = job_skills
108
+
109
+ m = MultipartEncoder(fields=fields)
110
+
111
+ response = self._client._request(
112
+ "POST",
113
+ "/api/v1/client/job",
114
+ data=m,
115
+ headers={"Content-Type": m.content_type},
116
+ )
117
+
118
+ return UserJobCreatedByUserResponse(**response.json())
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations
2
+ from typing import TYPE_CHECKING, Optional
3
+ from requests_toolbelt.multipart.encoder import MultipartEncoder
4
+
5
+ # import sys
6
+ # import os
7
+ # sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
8
+
9
+ from letit.schemas.micropost import PostType
10
+ from letit.schemas.common import CreatedWithPublicIdAndLinkResponse
11
+
12
+ if TYPE_CHECKING:
13
+ from ..client import LetIt
14
+
15
+ class MicropostResource:
16
+ def __init__(self, client: "LetIt"):
17
+ self._client = client
18
+
19
+ def client_create_micropost(
20
+ self,
21
+ body: str,
22
+ title: Optional[str] = None,
23
+ post_type: PostType = PostType.TEXT,
24
+ community_name: Optional[str] = None,
25
+ parent_micropost_public_id: Optional[str] = None,
26
+ parent_micropost_comment_public_id: Optional[str] = None,
27
+ allow_comments: bool = True,
28
+ is_draft: bool = False,
29
+ file: Optional[tuple] = None, # ("filename.png", open(..., "rb"), "image/png")
30
+ ) -> CreatedWithPublicIdAndLinkResponse:
31
+ """
32
+ A client creates a micropost.
33
+
34
+ Args:
35
+ body: The content of the post.
36
+ title: Required for original posts.
37
+ post_type: "TEXT" or "MEDIA". Defaults to "TEXT".
38
+ community_name: Optional community to post in.
39
+ parent_micropost_public_id: For replies.
40
+ parent_micropost_comment_public_id: For nested replies.
41
+ allow_comments: Whether comments are allowed. Defaults to True.
42
+ is_draft: Save as draft. Defaults to False.
43
+ file: Tuple of (filename, file_object, mime_type) for MEDIA posts.
44
+
45
+ Returns:
46
+ MicropostResponse with public_id and link.
47
+
48
+ Example:
49
+ # Text post
50
+ post = client.micropost.create(title="Hello", body="World")
51
+
52
+ # Media post
53
+ with open("photo.png", "rb") as f:
54
+ post = client.micropost.create(
55
+ title="My photo",
56
+ body="Check this out",
57
+ post_type="MEDIA",
58
+ file=("photo.png", f, "image/png"),
59
+ )
60
+ """
61
+
62
+ fields = {
63
+ "body": body,
64
+ "post_type": post_type,
65
+ "allow_comments": str(allow_comments).lower(),
66
+ "is_draft": str(is_draft).lower(),
67
+ }
68
+
69
+ if title:
70
+ fields["title"] = title
71
+ if community_name:
72
+ fields["community_name"] = community_name
73
+ if parent_micropost_public_id:
74
+ fields["parent_micropost_public_id"] = parent_micropost_public_id
75
+ if parent_micropost_comment_public_id:
76
+ fields["parent_micropost_comment_public_id"] = parent_micropost_comment_public_id
77
+ if file:
78
+ fields["file"] = file # tuple: ("filename.png", open(...), "image/png")
79
+
80
+ m = MultipartEncoder(fields=fields)
81
+
82
+ response = self._client._request(
83
+ "POST",
84
+ "/api/v1/client/micropost",
85
+ data=m,
86
+ headers={"Content-Type": m.content_type},
87
+ )
88
+
89
+ return CreatedWithPublicIdAndLinkResponse(**response.json())
File without changes
@@ -0,0 +1,5 @@
1
+ from pydantic import BaseModel
2
+
3
+ class CreatedWithPublicIdAndLinkResponse(BaseModel):
4
+ public_id: str
5
+ link: str
@@ -0,0 +1,53 @@
1
+ from enum import Enum, unique
2
+ from pydantic import BaseModel
3
+
4
+ @unique
5
+ class JobLocation(str, Enum):
6
+ REMOTE = "REMOTE"
7
+ ONSITE = "ONSITE"
8
+ HYBRID = "HYBRID"
9
+
10
+
11
+ @unique
12
+ class JobType(str, Enum):
13
+ FULLTIME = "FULLTIME"
14
+ PARTTIME = "PARTTIME"
15
+ CONTRACT = "CONTRACT"
16
+ FREELANCE = "FREELANCE"
17
+ INTERNSHIP = "INTERNSHIP"
18
+
19
+
20
+ @unique
21
+ class JobCategory(str, Enum):
22
+ PROGRAMMING = "PROGRAMMING"
23
+ BLOCKCHAIN = "BLOCKCHAIN"
24
+ DESIGN = "DESIGN"
25
+ MARKETING = "MARKETING"
26
+ CUSTOMERSUPPORT = "CUSTOMERSUPPORT"
27
+ WRITING = "WRITING"
28
+ PRODUCT = "PRODUCT"
29
+ SERVICE = "SERVICE"
30
+ HUMANRESOURCE = "HUMANRESOURCE"
31
+ ELSE = "ELSE"
32
+
33
+
34
+ @unique
35
+ class JobExperienceLevel(str, Enum):
36
+ ALL = "ALL"
37
+ JUNIOR = "JUNIOR"
38
+ MID = "MID"
39
+ SENIOR = "SENIOR"
40
+ NOEXPERIENCEREQUIRED = "NOEXPERIENCEREQUIRED"
41
+
42
+
43
+ @unique
44
+ class JobStatus(str, Enum):
45
+ DRAFT = "DRAFT"
46
+ PAID = "PAID"
47
+ CONFIRMED = "CONFIRMED"
48
+ HOLD = "HOLD"
49
+ REVIEW = "REVIEW"
50
+ CLOSED = "CLOSED"
51
+
52
+ class UserJobCreatedByUserResponse(BaseModel):
53
+ slug: str
@@ -0,0 +1,6 @@
1
+ from enum import Enum, unique
2
+
3
+ @unique
4
+ class PostType(str, Enum):
5
+ TEXT = "TEXT"
6
+ MEDIA = "MEDIA"
@@ -0,0 +1 @@
1
+ DEFAULT_API_SERVER = "https://api.letit.com"
@@ -0,0 +1,46 @@
1
+ # Unit tests
2
+
3
+ import httpx
4
+ import pytest
5
+ from unittest.mock import MagicMock, patch
6
+
7
+ import sys
8
+ import os
9
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
10
+
11
+ from client import LetIt
12
+
13
+ # $pip install -e .
14
+ # $LETIT_API_TOKEN=yours python -m pytest letit/tests/test_letit_integration.py -v -s
15
+ # @pytest.mark.skip(reason="already tested")
16
+ def test_client_create_micropost():
17
+ client = LetIt(api_token=os.environ["LETIT_API_TOKEN"])
18
+ try:
19
+ response = client.micropost.client_create_micropost(title="Test", body="Hello")
20
+ assert response.public_id is not None
21
+ except Exception as e:
22
+ if hasattr(e, 'response'):
23
+ print(repr(e.response.text))
24
+ raise
25
+
26
+
27
+ @pytest.mark.skip(reason="already tested")
28
+ def test_create_user_job_with_company():
29
+ client = LetIt(api_token=os.environ["LETIT_API_TOKEN"])
30
+ try:
31
+ with open("letit/tests/test_logo.png", "rb") as f:
32
+ response = client.job.client_create_user_job_with_company(
33
+ company_name="LetIt",
34
+ company_description="A test company.",
35
+ company_logo=("logo.png", f, "image/png"),
36
+ company_website="https://letit.com",
37
+ job_title="Test Engineers",
38
+ job_description="Write tests all day.",
39
+ job_how_to_apply="https://letit.com/apply",
40
+ job_skills="Python, pytest",
41
+ )
42
+ assert response.slug is not None
43
+ except Exception as e:
44
+ if hasattr(e, 'response'):
45
+ print(repr(e.response.text))
46
+ raise
@@ -0,0 +1,52 @@
1
+ # Unit tests
2
+
3
+ import httpx
4
+ import pytest
5
+ from unittest.mock import MagicMock, patch
6
+
7
+ import sys
8
+ import os
9
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
10
+
11
+ from client import LetIt
12
+ from settings import DEFAULT_API_SERVER
13
+
14
+ # $pip install -e .
15
+ # $pytest letit/tests/test_letit_unit.py -v
16
+ @pytest.fixture
17
+ def client():
18
+ return LetIt(api_token="test_token")
19
+
20
+
21
+ def test_init(client):
22
+ assert client.api_token == "test_token"
23
+ assert client._headers == {"USER-API-TOKEN": "test_token"}
24
+ assert client.base_url == DEFAULT_API_SERVER
25
+
26
+
27
+ @patch("letit.client.httpx.request")
28
+ def test_request_success(mock_request, client):
29
+ mock_response = MagicMock()
30
+ mock_response.status_code = 200
31
+ mock_request.return_value = mock_response
32
+
33
+ response = client._request("GET", "/some/path")
34
+
35
+ mock_request.assert_called_once_with(
36
+ "GET",
37
+ f"{DEFAULT_API_SERVER}/some/path",
38
+ headers={"USER-API-TOKEN": "test_token"},
39
+ )
40
+ assert response == mock_response
41
+
42
+
43
+ @patch("letit.client.httpx.request")
44
+ def test_request_raises_on_error(mock_request, client):
45
+ mock_response = MagicMock()
46
+ mock_response.raise_for_status.side_effect = httpx.HTTPStatusError(
47
+ "404", request=MagicMock(), response=mock_response
48
+ )
49
+ mock_request.return_value = mock_response
50
+
51
+ with pytest.raises(httpx.HTTPStatusError):
52
+ client._request("GET", "/bad/path")
@@ -0,0 +1,136 @@
1
+ Metadata-Version: 2.4
2
+ Name: letit
3
+ Version: 0.0.1
4
+ Summary: Python SDK for the LetIt API
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: requests>=2.32.0
9
+ Dynamic: license-file
10
+
11
+ # LetIt Python
12
+
13
+ The official Python SDK for the [LetIt](https://letit.com) API.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install letit
19
+ ```
20
+
21
+ ## Setup
22
+
23
+ Find your API token at [https://letit.com/settings/developer](https://letit.com/settings/developer) after creating an account at [https://letit.com/register](https://letit.com/register).
24
+
25
+ ```python
26
+ from letit import LetIt
27
+
28
+ client = LetIt(api_token="your_api_token_here")
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ### Create a micropost
34
+
35
+ ```python
36
+ from letit.schemas.micropost import PostType
37
+
38
+ # Text post
39
+ post = client.micropost.client_create_micropost(
40
+ title="Hello World",
41
+ body="This is my first post.",
42
+ )
43
+ print(post.public_id)
44
+ print(post.link)
45
+
46
+ # Media post
47
+ with open("photo.png", "rb") as f:
48
+ post = client.micropost.client_create_micropost(
49
+ title="My photo",
50
+ body="Check this out",
51
+ post_type=PostType.MEDIA,
52
+ file=("photo.png", f, "image/png"),
53
+ )
54
+ ```
55
+
56
+ ### Create a job post
57
+
58
+ ```python
59
+ from letit.schemas.job import JobLocation, JobType, JobCategory, JobExperienceLevel
60
+
61
+ job = client.job.client_create_user_job_with_company(
62
+ company_name="LetIt",
63
+ company_description="We build things.",
64
+ company_website="https://letit.com",
65
+ job_title="Rust Engineer",
66
+ job_description="Build backend services in Rust.",
67
+ job_how_to_apply="https://letit.com/careers",
68
+ job_location=JobLocation.REMOTE,
69
+ job_type=JobType.FULLTIME,
70
+ job_category=JobCategory.PROGRAMMING,
71
+ job_experience_level=JobExperienceLevel.SENIOR,
72
+ job_skills="Rust, SQL",
73
+ )
74
+ print(job.slug)
75
+ ```
76
+
77
+ ## API Reference
78
+
79
+ ### `LetIt(api_token, base_url?)`
80
+
81
+ | Parameter | Type | Description |
82
+ |-----------|------|-------------|
83
+ | `api_token` | `str` | Your API token |
84
+ | `base_url` | `str` | API base URL (default: `https://api.letit.com`) |
85
+
86
+ ### `client.micropost.client_create_micropost(...)`
87
+
88
+ | Parameter | Type | Default | Description |
89
+ |-----------|------|---------|-------------|
90
+ | `body` | `str` | required | Content of the post |
91
+ | `title` | `str` | `None` | Required for original posts |
92
+ | `post_type` | `PostType` | `PostType.TEXT` | `PostType.TEXT` or `PostType.MEDIA` |
93
+ | `community_name` | `str` | `None` | Community to post in |
94
+ | `parent_micropost_public_id` | `str` | `None` | For replies |
95
+ | `parent_micropost_comment_public_id` | `str` | `None` | For nested replies |
96
+ | `allow_comments` | `bool` | `True` | Whether comments are allowed |
97
+ | `is_draft` | `bool` | `False` | Save as draft |
98
+ | `file` | `tuple` | `None` | `(filename, file_object, mime_type)` for MEDIA posts |
99
+
100
+ ### `client.job.client_create_user_job_with_company(...)`
101
+
102
+ | Parameter | Type | Default | Description |
103
+ |-----------|------|---------|-------------|
104
+ | `company_name` | `str` | required | Name of the company |
105
+ | `company_description` | `str` | required | Description of the company |
106
+ | `company_website` | `str` | required | Company website URL |
107
+ | `job_title` | `str` | required | Title of the job |
108
+ | `job_description` | `str` | required | Full job description |
109
+ | `job_how_to_apply` | `str` | required | URL or instructions to apply |
110
+ | `company_logo` | `tuple` | `None` | `(filename, file_object, mime_type)` |
111
+ | `company_location` | `str` | `None` | Company location |
112
+ | `job_location` | `JobLocation` | `JobLocation.REMOTE` | `REMOTE`, `ONSITE`, `HYBRID` |
113
+ | `job_type` | `JobType` | `JobType.FULLTIME` | `FULLTIME`, `PARTTIME`, `CONTRACT`, `FREELANCE`, `INTERNSHIP` |
114
+ | `job_category` | `JobCategory` | `JobCategory.PROGRAMMING` | `PROGRAMMING`, `BLOCKCHAIN`, `DESIGN`, `MARKETING`, `CUSTOMERSUPPORT`, `WRITING`, `PRODUCT`, `SERVICE`, `HUMANRESOURCE`, `ELSE` |
115
+ | `job_experience_level` | `JobExperienceLevel` | `JobExperienceLevel.ALL` | `ALL`, `JUNIOR`, `MID`, `SENIOR`, `NOEXPERIENCEREQUIRED` |
116
+ | `job_minimum_salary` | `int` | `None` | Minimum salary |
117
+ | `job_maximum_salary` | `int` | `None` | Maximum salary |
118
+ | `job_pay_in_cryptocurrency` | `bool` | `False` | Pay in cryptocurrency |
119
+ | `job_skills` | `str` | `None` | Comma-separated skills |
120
+
121
+ ## Development
122
+
123
+ ```bash
124
+ # Install in editable mode
125
+ pip install -e .
126
+
127
+ # Run unit tests
128
+ python -m pytest letit/tests/test_letit.py -v
129
+
130
+ # Run integration tests (requires real API token)
131
+ LETIT_API_TOKEN=your_token python -m pytest letit/tests/test_letit_integration.py -v -s
132
+ ```
133
+
134
+ ## License
135
+
136
+ MIT
@@ -0,0 +1,20 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ letit/__init__.py
5
+ letit/client.py
6
+ letit/settings.py
7
+ letit.egg-info/PKG-INFO
8
+ letit.egg-info/SOURCES.txt
9
+ letit.egg-info/dependency_links.txt
10
+ letit.egg-info/requires.txt
11
+ letit.egg-info/top_level.txt
12
+ letit/resources/__init__.py
13
+ letit/resources/job.py
14
+ letit/resources/micropost.py
15
+ letit/schemas/__init__.py
16
+ letit/schemas/common.py
17
+ letit/schemas/job.py
18
+ letit/schemas/micropost.py
19
+ letit/tests/test_letit_integration.py
20
+ letit/tests/test_letit_unit.py
@@ -0,0 +1 @@
1
+ requests>=2.32.0
@@ -0,0 +1 @@
1
+ letit
@@ -0,0 +1,18 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "letit"
7
+ # version = "0.0.3" # Test
8
+ version = "0.0.1" # Prod
9
+ description = "Python SDK for the LetIt API"
10
+ readme = "README.md"
11
+ requires-python = ">=3.9"
12
+ dependencies = [
13
+ # "httpx>=0.27.0"
14
+ "requests>=2.32.0"
15
+ ]
16
+
17
+ [tool.setuptools.packages.find]
18
+ include = ["letit*"]
letit-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+