feat(types): username + individual validation

This commit is contained in:
dextmorgn
2025-12-05 15:46:21 +01:00
parent 7562da12a6
commit 4a74674c4d
4 changed files with 40 additions and 31 deletions

View File

@@ -411,6 +411,7 @@ class Individual(FlowsintType):
self.label = self.first_name
elif self.last_name:
self.label = self.last_name
self.full_name = self.label
return self
@classmethod

View File

@@ -13,14 +13,16 @@ class Username(FlowsintType):
...,
description="Username or handle string",
title="Username value",
json_schema_extra={"primary": True}
json_schema_extra={"primary": True},
)
platform: Optional[str] = Field(
None, description="Platform name, e.g., 'twitter'", title="Username platform"
)
platform: Optional[str] = Field(None, description="Platform name, e.g., 'twitter'", title="Username platform")
last_seen: Optional[str] = Field(
None, description="Last time this username was observed", title="Last seen at"
)
@field_validator('value')
@field_validator("value")
@classmethod
def validate_username(cls, v: str) -> str:
"""Validate username format.
@@ -31,13 +33,15 @@ class Username(FlowsintType):
- Underscores (_)
- Hyphens (-)
"""
if v.startswith("@"):
v = v[1:] # We remove it
if not re.match(r"^[a-zA-Z0-9_-]{3,80}$", v):
raise ValueError(
f"Invalid username: {v}. Must be 3-80 characters and contain only letters, numbers, underscores, and hyphens."
)
return v
@model_validator(mode='after')
@model_validator(mode="after")
def compute_label(self) -> Self:
self.label = f"{self.value}"
return self

View File

@@ -1,8 +1,21 @@
from flowsint_types import (
Domain, Ip, Individual, Email, Phone, Organization,
Username, Credential, CryptoWallet, CryptoNFT,
CryptoWalletTransaction, SocialAccount, Website,
Port, CIDR, ASN, Location
Domain,
Ip,
Individual,
Email,
Phone,
Organization,
Username,
Credential,
CryptoWallet,
CryptoNFT,
CryptoWalletTransaction,
SocialAccount,
Website,
Port,
CIDR,
ASN,
Location,
)
@@ -19,6 +32,7 @@ def test_domain_label():
def test_individual_label():
individual = Individual(first_name="John", last_name="Doe")
assert individual.label == "John Doe"
assert individual.full_name == "John Doe"
def test_email_label():
@@ -52,6 +66,10 @@ def test_organization_label_with_nom_raison_sociale():
def test_username_label():
username = Username(value="johndoe")
assert username.label == "johndoe"
def test_username_label_2():
username = Username(value="@johndoe")
assert username.label == "johndoe"
def test_credential_label():
@@ -77,7 +95,7 @@ def test_crypto_nft_label_with_name():
wallet=wallet,
contract_address="0x123d35Cc6634C0532925a3b844Bc454e4438f123",
token_id="1234",
name="Cool NFT"
name="Cool NFT",
)
assert nft.label == "Cool NFT"
@@ -88,7 +106,7 @@ def test_crypto_nft_label_with_collection():
wallet=wallet,
contract_address="0x123d35Cc6634C0532925a3b844Bc454e4438f123",
token_id="1234",
collection_name="Bored Apes"
collection_name="Bored Apes",
)
assert nft.label == "Bored Apes #1234"
@@ -98,27 +116,21 @@ def test_crypto_nft_label_fallback_uid():
nft = CryptoNFT(
wallet=wallet,
contract_address="0x123d35Cc6634C0532925a3b844Bc454e4438f123",
token_id="1234"
token_id="1234",
)
assert nft.label == "0x123d35Cc6634C0532925a3b844Bc454e4438f123:1234"
def test_crypto_wallet_transaction_label_with_hash():
source_wallet = CryptoWallet(address="0x742d35Cc6634C0532925a3b844Bc454e4438f44e")
transaction = CryptoWalletTransaction(
source=source_wallet,
hash="0xabc123def456"
)
transaction = CryptoWalletTransaction(source=source_wallet, hash="0xabc123def456")
assert transaction.label == "0xabc123def456"
def test_crypto_wallet_transaction_label_with_source_and_target():
source_wallet = CryptoWallet(address="0x742d35Cc6634C0532925a3b844Bc454e4438f44e")
target_wallet = CryptoWallet(address="0x123d35Cc6634C0532925a3b844Bc454e4438f123")
transaction = CryptoWalletTransaction(
source=source_wallet,
target=target_wallet
)
transaction = CryptoWalletTransaction(source=source_wallet, target=target_wallet)
assert transaction.label == "Transaction from 0x742d35... to 0x123d35..."
@@ -131,19 +143,14 @@ def test_crypto_wallet_transaction_label_source_only():
def test_social_account_label_with_display_name():
username = Username(value="johndoe")
account = SocialAccount(
username=username,
display_name="John Doe",
platform="twitter"
username=username, display_name="John Doe", platform="twitter"
)
assert account.label == "John Doe (@johndoe)"
def test_social_account_label_without_display_name():
username = Username(value="johndoe")
account = SocialAccount(
username=username,
platform="twitter"
)
account = SocialAccount(username=username, platform="twitter")
assert account.label == "@johndoe"
@@ -187,9 +194,6 @@ def test_asn_label_without_name():
def test_location_label():
location = Location(
address="123 Main St",
city="Paris",
country="France",
zip="75001"
address="123 Main St", city="Paris", country="France", zip="75001"
)
assert location.label == "123 Main St, Paris, France"

View File

@@ -1,4 +1,4 @@
from flowsint_types import Domain, Ip, get_type
from flowsint_types import Domain, Individual, Ip, get_type
from flowsint_types.registry import TYPE_REGISTRY