Release v1.0.0
Some checks failed
CI / Checks (push) Failing after 13m2s

This commit is contained in:
2025-08-20 21:00:50 +02:00
commit b4338e2769
401 changed files with 23576 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
# HotPocket by BTHLabs
This repository contains the _HotPocket Common_ project.

View File

@@ -0,0 +1,2 @@
from .associations import AssociationsSearchMode # noqa: F401
from .core import NULL_UUID, App, Env # noqa: F401

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import enum
class AssociationsSearchMode(enum.Enum):
DEFAULT = ''
STARRED = 'STARRED'
ARCHIVED = 'ARCHIVED'

View File

@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import enum
import uuid
NULL_UUID = uuid.UUID('00000000-0000-0000-0000-000000000000')
class Env(enum.Enum):
METAL = 'metal'
DOCKER = 'docker'
DEPLOYMENT = 'deployment'
AIO = 'aio'
class App(enum.Enum):
ADMIN = 'admin'
WEBAPP = 'webapp'

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import typing
from django.db.models.fields import Field
from django.db.models.lookups import PatternLookup
class BaseLikeLookup(PatternLookup):
"""
This class provides base for custom LIKE/ILIKE lookups for PostgreSQL.
Unlike the builtin `contains` and `icontains`, these lookups do not do
any DB-side processing of parameters. This opens up the ability to use
index ops provided by `pg_trgm` to better index text columns for pattern
searches.
These lookups currently support only PostgreSQL 15+.
"""
# This is the pattern operator used to generate SQL for the lookup
# when the right hand side isn't a raw string. Subclasses must specify this
# or the whole thing will blow up ;).
PATTERN_OP: str | None = None
# This is the rhs operator used to generate SQL for the lookup when the
# right hand side is a raw string. Subclasses must specify this or the
# whole thing will blow up ;).
RHS_OP: str | None = None
def get_rhs_op(self, connection: typing.Any, rhs: typing.Any) -> typing.Any:
if hasattr(self.rhs, 'as_sql') or self.bilateral_transforms:
pattern = typing.cast(str, self.PATTERN_OP).format(
connection.pattern_esc,
)
return pattern.format(rhs)
else:
return typing.cast(str, self.RHS_OP)
@Field.register_lookup
class Like(BaseLikeLookup):
PATTERN_OP = "LIKE '%%' || {} || '%%'"
RHS_OP = 'LIKE %s'
lookup_name = 'like'
@Field.register_lookup
class ILike(BaseLikeLookup):
PATTERN_OP = "ILIKE '%%' || {} || '%%'"
RHS_OP = 'ILIKE %s'
lookup_name = 'ilike'

View File

@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import importlib
import typing
def load_module_attribute(import_path: str) -> typing.Any:
module_path, attribute = import_path.split(':')
module = importlib.import_module(module_path)
if attribute is not None:
return getattr(module, attribute)
return module

View File

@@ -0,0 +1,164 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import pathlib
import typing
import urllib.parse
class URL:
"""
The URL class.
Example:
.. code-block:: python
url = URL('https://ziomek.dog/').\
set_path('/index_old.html').\
set_query({
'test': 'true',
}).\
to_string()
The *encoding* argument will be used to decode *initial* if it's
```bytes``. Also, it'll be used when decoding QS.
"""
def __init__(self,
initial: str | bytes | pathlib.Path | urllib.parse.ParseResult,
encoding: str = 'utf-8'):
self.encoding = encoding
self.parsed_url: urllib.parse.ParseResult
if isinstance(initial, urllib.parse.ParseResult):
self.parsed_url = initial
elif isinstance(initial, pathlib.Path):
self.parsed_url = urllib.parse.urlparse(str(initial))
elif isinstance(initial, bytes):
self.parsed_url = urllib.parse.urlparse(
initial.decode(self.encoding),
)
else:
self.parsed_url = urllib.parse.urlparse(initial)
def replace_parsed_url_field(self, field: str, value: typing.Any):
self.parsed_url = self.parsed_url._replace(**{field: value})
return self
def append_path_component(self, component: str | pathlib.Path) -> URL:
_ = self.set_path(self.path / component)
return self
@property
def scheme(self) -> str:
return self.parsed_url.scheme
def set_scheme(self, new_scheme: str) -> URL:
self.replace_parsed_url_field('scheme', new_scheme)
return self
@property
def netloc(self) -> str:
return self.parsed_url.netloc
def set_netloc(self, new_netloc: str) -> URL:
self.replace_parsed_url_field('netloc', new_netloc)
return self
@property
def hostname(self) -> str | None:
return self.parsed_url.hostname
@property
def port(self) -> int | None:
return self.parsed_url.port
@property
def username(self) -> str | None:
return self.parsed_url.username
@property
def password(self) -> str | None:
return self.parsed_url.password
@property
def path(self) -> pathlib.PurePosixPath:
return pathlib.PurePosixPath(self.parsed_url.path)
def set_path(self, new_path: str | pathlib.Path | pathlib.PurePath) -> URL:
self.replace_parsed_url_field(
'path', str(pathlib.PurePosixPath(new_path)),
)
return self
@property
def params(self) -> str:
return self.parsed_url.params
def set_params(self, new_params: str) -> URL:
self.replace_parsed_url_field('params', new_params)
return self
@property
def query(self) -> dict[typing.AnyStr, list[typing.AnyStr]]:
if self.parsed_url.query == '':
return {}
return urllib.parse.parse_qs(
self.parsed_url.query, encoding=self.encoding, # type: ignore[arg-type]
)
def set_query(self,
new_query: dict[typing.AnyStr, list[typing.AnyStr]] | list,
) -> URL:
self.replace_parsed_url_field(
'query', urllib.parse.urlencode(new_query, doseq=True),
)
return self
@property
def query_string(self) -> str:
return self.parsed_url.query
def set_query_string(self, new_query_string: str) -> URL:
self.replace_parsed_url_field('query', new_query_string)
return self
@property
def fragment(self) -> str:
return self.parsed_url.fragment
def set_fragment(self, new_fragment: str) -> URL:
self.replace_parsed_url_field('fragment', new_fragment)
return self
def to_string(self) -> str:
return urllib.parse.urlunparse(self.parsed_url)
def __truediv__(self, component: str | pathlib.Path) -> URL:
return self.append_path_component(component)
def __mod__(self,
query: dict[typing.AnyStr, list[typing.AnyStr]],
) -> URL:
return self.set_query(
{**self.query, **query},
)
def __str__(self) -> str:
return self.to_string()
def __repr__(self) -> str:
return f'<URL {self}>'
def __bytes__(self) -> bytes:
return self.__str__().encode(self.encoding)

View File

@@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
import secrets
from uuid6 import UUID
def uuid7_from_timestamp(timestamp: int) -> UUID:
# The code below was shamelessly taken from `uuid6-python` package.
# Copyright (c) 2021 oittaa | MIT License
uuid_int = ((timestamp * 1_000) & 0xFFFFFFFFFFFF) << 80
uuid_int |= secrets.randbits(76)
return UUID(int=uuid_int, version=7)

View File

@@ -0,0 +1,16 @@
[tool.poetry]
name = "hotpocket-common"
version = "1.0.0.dev0"
description = "HotPocket Common"
authors = ["Tomek Wójcik <contact@bthlabs.pl>"]
license = "Apache-2.0"
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
django = "5.2.3"
uuid6 = "2025.0.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"