constec 0.1.0__py3-none-any.whl → 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,62 @@
1
+ from django.db import models
2
+ from .base import UUIDModel
3
+
4
+
5
+ class Organization(UUIDModel):
6
+ """Platform-level tenant (e.g., Constec itself)."""
7
+ name = models.CharField(max_length=255)
8
+ slug = models.SlugField(max_length=100, unique=True)
9
+ description = models.TextField(blank=True, null=True)
10
+
11
+ class Meta:
12
+ app_label = 'constec_db'
13
+ db_table = 'core"."organizations'
14
+
15
+ def __str__(self):
16
+ return self.name
17
+
18
+
19
+ class OrganizationRole(UUIDModel):
20
+ """Roles within an organization."""
21
+ organization = models.ForeignKey(
22
+ Organization,
23
+ on_delete=models.CASCADE,
24
+ related_name="organization_roles",
25
+ null=True,
26
+ blank=True
27
+ )
28
+ name = models.CharField(max_length=100)
29
+ description = models.TextField(blank=True, null=True)
30
+ permissions = models.JSONField(default=dict)
31
+ is_system_role = models.BooleanField(default=False)
32
+
33
+ class Meta:
34
+ app_label = 'constec_db'
35
+ db_table = 'core"."organization_roles'
36
+ unique_together = [['organization', 'name']]
37
+
38
+ def __str__(self):
39
+ return self.name
40
+
41
+
42
+ class OrganizationUser(UUIDModel):
43
+ """Administrator users at organization level."""
44
+ organization = models.ForeignKey(
45
+ Organization,
46
+ on_delete=models.CASCADE,
47
+ related_name="users",
48
+ )
49
+ name = models.CharField(max_length=255)
50
+ email = models.EmailField(unique=True)
51
+ password_hash = models.CharField(max_length=255)
52
+ role = models.ForeignKey(
53
+ OrganizationRole,
54
+ on_delete=models.PROTECT
55
+ )
56
+
57
+ class Meta:
58
+ app_label = 'constec_db'
59
+ db_table = 'core"."organization_users'
60
+
61
+ def __str__(self):
62
+ return f"{self.name} ({self.email})"
@@ -0,0 +1,28 @@
1
+ from django.db import models
2
+ from .base import UUIDModel
3
+ from .company import Company
4
+
5
+
6
+ class Person(UUIDModel):
7
+ """Real person (customer, supplier, employee)."""
8
+ company = models.ForeignKey(
9
+ Company,
10
+ on_delete=models.CASCADE,
11
+ related_name="persons",
12
+ )
13
+ first_name = models.CharField(max_length=100)
14
+ last_name = models.CharField(max_length=100)
15
+ full_name = models.CharField(max_length=255)
16
+ metadata = models.JSONField(default=dict, blank=True)
17
+
18
+ class Meta:
19
+ app_label = 'constec_db'
20
+ db_table = 'core"."persons'
21
+
22
+ def __str__(self):
23
+ return f"{self.full_name}"
24
+
25
+ def save(self, *args, **kwargs):
26
+ """Auto-generate full_name from first_name and last_name."""
27
+ self.full_name = f"{self.first_name} {self.last_name}"
28
+ super().save(*args, **kwargs)
@@ -0,0 +1,94 @@
1
+ from django.db import models
2
+ from .base import UUIDModel
3
+ from .company import Company
4
+ from .person import Person
5
+ from .flow import Flow
6
+
7
+
8
+ class Session(UUIDModel):
9
+ """Conversation session between a user and the AI agent.
10
+
11
+ Tracks the conversation context including company, user, and permissions.
12
+ All MCP tools receive the session_id to know who is making the request.
13
+ """
14
+
15
+ company = models.ForeignKey(
16
+ Company,
17
+ on_delete=models.CASCADE,
18
+ related_name="sessions",
19
+ help_text="The company this session belongs to"
20
+ )
21
+ user = models.ForeignKey(
22
+ Person,
23
+ on_delete=models.CASCADE,
24
+ related_name="sessions",
25
+ help_text="The person chatting (can be employee or client)"
26
+ )
27
+ flow = models.ForeignKey(
28
+ Flow,
29
+ on_delete=models.PROTECT,
30
+ related_name="sessions",
31
+ help_text="AI flow configuration to use"
32
+ )
33
+
34
+ metadata = models.JSONField(
35
+ default=dict,
36
+ help_text="Session context (connection, role, etc.)"
37
+ )
38
+
39
+ is_active = models.BooleanField(default=True)
40
+ ended_at = models.DateTimeField(null=True, blank=True)
41
+ created_at = models.DateTimeField(auto_now_add=True)
42
+ updated_at = models.DateTimeField(auto_now=True)
43
+
44
+ class Meta:
45
+ app_label = 'constec_db'
46
+ db_table = 'constancia"."sessions'
47
+ indexes = [
48
+ models.Index(fields=['company', 'is_active']),
49
+ models.Index(fields=['user', 'is_active']),
50
+ models.Index(fields=['-created_at']),
51
+ ]
52
+
53
+ def __str__(self):
54
+ return f"Session {self.id} - {self.user} @ {self.company.name}"
55
+
56
+
57
+ class Message(UUIDModel):
58
+ """Message in a conversation session.
59
+
60
+ Stores the conversation history between user and AI assistant.
61
+ """
62
+
63
+ ROLE_CHOICES = [
64
+ ('user', 'User'),
65
+ ('assistant', 'Assistant'),
66
+ ]
67
+
68
+ session = models.ForeignKey(
69
+ Session,
70
+ on_delete=models.CASCADE,
71
+ related_name="messages",
72
+ )
73
+ role = models.CharField(max_length=20, choices=ROLE_CHOICES)
74
+ content = models.TextField()
75
+
76
+ metadata = models.JSONField(
77
+ default=dict,
78
+ blank=True,
79
+ help_text="Additional message data: tool calls, errors, context, etc."
80
+ )
81
+
82
+ created_at = models.DateTimeField(auto_now_add=True)
83
+
84
+ class Meta:
85
+ app_label = 'constec_db'
86
+ db_table = 'constancia"."messages'
87
+ ordering = ['created_at']
88
+ indexes = [
89
+ models.Index(fields=['session', 'created_at']),
90
+ ]
91
+
92
+ def __str__(self):
93
+ preview = self.content[:50] + "..." if len(self.content) > 50 else self.content
94
+ return f"{self.role}: {preview}"
@@ -0,0 +1,68 @@
1
+ from django.db import models
2
+ from .base import UUIDModel
3
+ from .person import Person
4
+ from .company import Company
5
+
6
+
7
+ class TagCategory(UUIDModel):
8
+ """Tag categories per company."""
9
+ company = models.ForeignKey(
10
+ Company,
11
+ on_delete=models.CASCADE,
12
+ related_name="tag_categories",
13
+ )
14
+ name = models.CharField(max_length=50)
15
+ description = models.TextField(blank=True, null=True)
16
+
17
+ class Meta:
18
+ app_label = 'constec_db'
19
+ db_table = 'core"."tag_categories'
20
+
21
+ def __str__(self):
22
+ return f"{self.name}"
23
+
24
+
25
+ class PersonTag(UUIDModel):
26
+ """Tags for persons."""
27
+ company = models.ForeignKey(
28
+ Company,
29
+ on_delete=models.CASCADE,
30
+ related_name="person_tags",
31
+ )
32
+ category = models.ForeignKey(
33
+ TagCategory,
34
+ on_delete=models.CASCADE,
35
+ related_name="person_tags",
36
+ )
37
+ name = models.CharField(max_length=50)
38
+ color = models.CharField(max_length=7, blank=True, null=True)
39
+
40
+ class Meta:
41
+ app_label = 'constec_db'
42
+ db_table = 'core"."person_tags'
43
+ unique_together = [['company', 'category', 'name']]
44
+
45
+ def __str__(self):
46
+ return f"{self.name}"
47
+
48
+
49
+ class PersonTagged(UUIDModel):
50
+ """Person-tag relationship."""
51
+ person = models.ForeignKey(
52
+ Person,
53
+ on_delete=models.CASCADE,
54
+ related_name="tagged_items",
55
+ )
56
+ tag = models.ForeignKey(
57
+ PersonTag,
58
+ on_delete=models.CASCADE,
59
+ related_name="tagged_persons",
60
+ )
61
+
62
+ class Meta:
63
+ app_label = 'constec_db'
64
+ db_table = 'core"."person_tagged'
65
+ unique_together = [['person', 'tag']]
66
+
67
+ def __str__(self):
68
+ return f"{self.person.full_name} - {self.tag.name}"
@@ -0,0 +1,72 @@
1
+ from django.db import models
2
+ from .base import UUIDModel
3
+ from .company import Company
4
+
5
+
6
+ class User(UUIDModel):
7
+ """User belonging to a company."""
8
+ company = models.ForeignKey(
9
+ Company,
10
+ on_delete=models.CASCADE,
11
+ related_name="users"
12
+ )
13
+ name = models.CharField(max_length=255)
14
+ email = models.EmailField(unique=True)
15
+ password_hash = models.CharField(max_length=255)
16
+
17
+ class Meta:
18
+ app_label = 'constec_db'
19
+ db_table = 'core"."users'
20
+
21
+ def __str__(self):
22
+ return f"{self.name} ({self.email})"
23
+
24
+
25
+ class UserRole(UUIDModel):
26
+ """Role within a company."""
27
+ company = models.ForeignKey(
28
+ Company,
29
+ on_delete=models.CASCADE,
30
+ related_name="user_roles",
31
+ null=True,
32
+ blank=True
33
+ )
34
+ name = models.CharField(max_length=100)
35
+ description = models.TextField(blank=True, null=True)
36
+ permissions = models.JSONField(default=dict)
37
+ is_system_role = models.BooleanField(default=False)
38
+
39
+ class Meta:
40
+ app_label = 'constec_db'
41
+ db_table = 'core"."user_roles'
42
+ unique_together = [['company', 'name']]
43
+
44
+ def __str__(self):
45
+ return f"{self.name}"
46
+
47
+
48
+ class UserCompanyAccess(UUIDModel):
49
+ """Cross-company access: one User can access multiple Companies."""
50
+ user = models.ForeignKey(
51
+ User,
52
+ on_delete=models.CASCADE,
53
+ related_name="company_accesses",
54
+ )
55
+ company = models.ForeignKey(
56
+ Company,
57
+ on_delete=models.CASCADE,
58
+ related_name="user_accesses",
59
+ )
60
+ role = models.ForeignKey(
61
+ UserRole,
62
+ on_delete=models.PROTECT,
63
+ related_name="user_accesses",
64
+ )
65
+
66
+ class Meta:
67
+ app_label = 'constec_db'
68
+ db_table = 'core"."user_company_access'
69
+ unique_together = [['user', 'company']]
70
+
71
+ def __str__(self):
72
+ return f"{self.user.email} @ {self.company.name} ({self.role.name})"
constec/py.typed ADDED
File without changes
@@ -0,0 +1,14 @@
1
+ """
2
+ constec.services - Shared services for the Constec platform.
3
+
4
+ Usage:
5
+ from constec.services import encrypt_password, decrypt_password
6
+ """
7
+
8
+ from .encryption import encrypt_password, decrypt_password, EncryptionService
9
+
10
+ __all__ = [
11
+ 'encrypt_password',
12
+ 'decrypt_password',
13
+ 'EncryptionService',
14
+ ]
@@ -0,0 +1,92 @@
1
+ """
2
+ Fernet encryption for sensitive data like database passwords.
3
+
4
+ Usage:
5
+ from constec.services import encrypt_password, decrypt_password
6
+
7
+ # Encrypt
8
+ encrypted = encrypt_password("my_password", fernet_key)
9
+
10
+ # Decrypt
11
+ password = decrypt_password(encrypted, fernet_key)
12
+
13
+ The FERNET_KEY should be stored in environment variables and never committed.
14
+ Generate a new key with:
15
+ from cryptography.fernet import Fernet
16
+ print(Fernet.generate_key().decode())
17
+ """
18
+
19
+ from cryptography.fernet import Fernet, InvalidToken
20
+
21
+
22
+ class EncryptionService:
23
+ """Service for encrypting and decrypting sensitive data."""
24
+
25
+ def __init__(self, key: str):
26
+ """
27
+ Initialize with Fernet key.
28
+
29
+ Args:
30
+ key: Fernet key as string (will be encoded to bytes)
31
+ """
32
+ self._fernet = Fernet(key.encode() if isinstance(key, str) else key)
33
+
34
+ def encrypt(self, plaintext: str) -> str:
35
+ """
36
+ Encrypt a string.
37
+
38
+ Args:
39
+ plaintext: String to encrypt
40
+
41
+ Returns:
42
+ Encrypted string (base64 encoded)
43
+ """
44
+ return self._fernet.encrypt(plaintext.encode()).decode()
45
+
46
+ def decrypt(self, ciphertext: str) -> str:
47
+ """
48
+ Decrypt a string.
49
+
50
+ Args:
51
+ ciphertext: Encrypted string (base64 encoded)
52
+
53
+ Returns:
54
+ Decrypted string
55
+
56
+ Raises:
57
+ InvalidToken: If decryption fails (wrong key or corrupted data)
58
+ """
59
+ return self._fernet.decrypt(ciphertext.encode()).decode()
60
+
61
+
62
+ def encrypt_password(password: str, fernet_key: str) -> str:
63
+ """
64
+ Encrypt a password using Fernet symmetric encryption.
65
+
66
+ Args:
67
+ password: Plain text password
68
+ fernet_key: Fernet encryption key
69
+
70
+ Returns:
71
+ Encrypted password (base64 encoded string)
72
+ """
73
+ service = EncryptionService(fernet_key)
74
+ return service.encrypt(password)
75
+
76
+
77
+ def decrypt_password(encrypted_password: str, fernet_key: str) -> str:
78
+ """
79
+ Decrypt a password using Fernet symmetric encryption.
80
+
81
+ Args:
82
+ encrypted_password: Encrypted password (base64 encoded string)
83
+ fernet_key: Fernet encryption key
84
+
85
+ Returns:
86
+ Decrypted password
87
+
88
+ Raises:
89
+ InvalidToken: If decryption fails
90
+ """
91
+ service = EncryptionService(fernet_key)
92
+ return service.decrypt(encrypted_password)
@@ -0,0 +1,20 @@
1
+ """
2
+ constec.utils - Shared utilities for the Constec platform.
3
+
4
+ Usage:
5
+ from constec.utils import normalize_cuit, validate_cuit
6
+ from constec.utils import hash_password, verify_password
7
+ """
8
+
9
+ from .cuit import normalize_cuit, validate_cuit, format_cuit
10
+ from .password import hash_password, verify_password
11
+
12
+ __all__ = [
13
+ # CUIT
14
+ 'normalize_cuit',
15
+ 'validate_cuit',
16
+ 'format_cuit',
17
+ # Password
18
+ 'hash_password',
19
+ 'verify_password',
20
+ ]
constec/utils/cuit.py ADDED
@@ -0,0 +1,107 @@
1
+ """
2
+ CUIT (Clave Única de Identificación Tributaria) utilities.
3
+
4
+ Argentine tax identification number format: XX-XXXXXXXX-X
5
+ - First 2 digits: Type (20=person, 23/24=company, 27=foreign, 30/33/34=company)
6
+ - Middle 8 digits: ID number
7
+ - Last digit: Verification digit
8
+
9
+ Usage:
10
+ from constec.utils import normalize_cuit, validate_cuit, format_cuit
11
+
12
+ # Normalize (remove dashes)
13
+ normalized = normalize_cuit("20-12345678-9") # "20123456789"
14
+
15
+ # Validate
16
+ is_valid = validate_cuit("20-12345678-9") # True/False
17
+
18
+ # Format (add dashes)
19
+ formatted = format_cuit("20123456789") # "20-12345678-9"
20
+ """
21
+
22
+ import re
23
+
24
+
25
+ def normalize_cuit(cuit: str) -> str:
26
+ """
27
+ Normalize CUIT by removing dashes and spaces.
28
+
29
+ Args:
30
+ cuit: CUIT string (with or without dashes)
31
+
32
+ Returns:
33
+ CUIT without dashes (11 digits)
34
+
35
+ Examples:
36
+ >>> normalize_cuit("20-12345678-9")
37
+ '20123456789'
38
+ >>> normalize_cuit("20 12345678 9")
39
+ '20123456789'
40
+ """
41
+ return re.sub(r'[\s\-]', '', cuit)
42
+
43
+
44
+ def format_cuit(cuit: str) -> str:
45
+ """
46
+ Format CUIT with dashes (XX-XXXXXXXX-X).
47
+
48
+ Args:
49
+ cuit: CUIT string (11 digits, with or without dashes)
50
+
51
+ Returns:
52
+ Formatted CUIT with dashes
53
+
54
+ Examples:
55
+ >>> format_cuit("20123456789")
56
+ '20-12345678-9'
57
+ """
58
+ normalized = normalize_cuit(cuit)
59
+ if len(normalized) != 11:
60
+ return cuit # Return as-is if invalid length
61
+ return f"{normalized[:2]}-{normalized[2:10]}-{normalized[10]}"
62
+
63
+
64
+ def _calculate_verification_digit(cuit_base: str) -> int:
65
+ """Calculate the CUIT verification digit using mod 11 algorithm."""
66
+ weights = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]
67
+ total = sum(int(d) * w for d, w in zip(cuit_base, weights))
68
+ remainder = total % 11
69
+
70
+ if remainder == 0:
71
+ return 0
72
+ elif remainder == 1:
73
+ return 9 # Special case for type 23 (women)
74
+ else:
75
+ return 11 - remainder
76
+
77
+
78
+ def validate_cuit(cuit: str) -> bool:
79
+ """
80
+ Validate a CUIT using the verification digit algorithm.
81
+
82
+ Args:
83
+ cuit: CUIT string (with or without dashes)
84
+
85
+ Returns:
86
+ True if valid, False otherwise
87
+
88
+ Examples:
89
+ >>> validate_cuit("20-12345678-9")
90
+ True # (if verification digit is correct)
91
+ """
92
+ normalized = normalize_cuit(cuit)
93
+
94
+ # Must be exactly 11 digits
95
+ if not re.match(r'^\d{11}$', normalized):
96
+ return False
97
+
98
+ # Valid type prefixes
99
+ valid_types = ['20', '23', '24', '27', '30', '33', '34']
100
+ if normalized[:2] not in valid_types:
101
+ return False
102
+
103
+ # Verify check digit
104
+ expected_digit = _calculate_verification_digit(normalized[:10])
105
+ actual_digit = int(normalized[10])
106
+
107
+ return expected_digit == actual_digit
@@ -0,0 +1,62 @@
1
+ """
2
+ Password hashing utilities using bcrypt.
3
+
4
+ Usage:
5
+ from constec.utils import hash_password, verify_password
6
+
7
+ # Hash a password
8
+ hashed = hash_password("my_password")
9
+
10
+ # Verify a password
11
+ is_valid = verify_password("my_password", hashed) # True
12
+ """
13
+
14
+ import bcrypt
15
+
16
+
17
+ def hash_password(password: str, rounds: int = 12) -> str:
18
+ """
19
+ Hash a password using bcrypt.
20
+
21
+ Args:
22
+ password: Plain text password
23
+ rounds: Cost factor (default 12, higher = slower but more secure)
24
+
25
+ Returns:
26
+ Bcrypt hash as string
27
+
28
+ Example:
29
+ >>> hashed = hash_password("secret123")
30
+ >>> hashed.startswith("$2b$")
31
+ True
32
+ """
33
+ salt = bcrypt.gensalt(rounds=rounds)
34
+ hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
35
+ return hashed.decode('utf-8')
36
+
37
+
38
+ def verify_password(password: str, hashed: str) -> bool:
39
+ """
40
+ Verify a password against a bcrypt hash.
41
+
42
+ Args:
43
+ password: Plain text password to verify
44
+ hashed: Bcrypt hash to verify against
45
+
46
+ Returns:
47
+ True if password matches, False otherwise
48
+
49
+ Example:
50
+ >>> hashed = hash_password("secret123")
51
+ >>> verify_password("secret123", hashed)
52
+ True
53
+ >>> verify_password("wrong", hashed)
54
+ False
55
+ """
56
+ try:
57
+ return bcrypt.checkpw(
58
+ password.encode('utf-8'),
59
+ hashed.encode('utf-8')
60
+ )
61
+ except (ValueError, TypeError):
62
+ return False
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: constec
3
- Version: 0.1.0
4
- Summary: Base library for the Constec ecosystem - shared utilities and namespace foundation
3
+ Version: 0.2.0
4
+ Summary: Base library for the Constec ecosystem - shared utilities, models, and namespace foundation
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/TpmyCT/constec-python
7
7
  Project-URL: Repository, https://github.com/TpmyCT/constec-python
@@ -15,9 +15,19 @@ Classifier: Programming Language :: Python :: 3.10
15
15
  Classifier: Programming Language :: Python :: 3.11
16
16
  Classifier: Programming Language :: Python :: 3.12
17
17
  Classifier: Typing :: Typed
18
+ Classifier: Framework :: Django :: 4.2
18
19
  Requires-Python: >=3.9
19
20
  Description-Content-Type: text/markdown
20
21
  License-File: LICENSE
22
+ Provides-Extra: db
23
+ Requires-Dist: Django>=4.2; extra == "db"
24
+ Provides-Extra: services
25
+ Requires-Dist: cryptography>=41.0; extra == "services"
26
+ Requires-Dist: bcrypt>=4.0; extra == "services"
27
+ Provides-Extra: utils
28
+ Requires-Dist: bcrypt>=4.0; extra == "utils"
29
+ Provides-Extra: all
30
+ Requires-Dist: constec[db,services,utils]; extra == "all"
21
31
  Dynamic: license-file
22
32
 
23
33
  # Constec
@@ -0,0 +1,33 @@
1
+ constec/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ constec/db/__init__.py,sha256=wou_y3aqCg1xP8Gr13FKRy0kEI2avV-19G3P2g3bzjs,287
3
+ constec/db/apps.py,sha256=zc-9lGNa049q9bvxV7957EwnZjuBozPLZq594cKAU24,221
4
+ constec/db/migrations/0001_initial.py,sha256=OdNHJg1IgsjcpaFgolFG9e5HFEQZkuoFtJ81pi1UZ5U,28224
5
+ constec/db/migrations/0002_alter_companysystem_table_alter_connection_table_and_more.py,sha256=e6ygltUq_ObFXykDn9I-aypqxtBvMQg-0NcuSMoqaOc,911
6
+ constec/db/migrations/0003_add_is_active_to_company.py,sha256=kfMpnxkkOuJH2W81GYoKmT3qHvooyi22BGuFOSVqbJY,433
7
+ constec/db/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ constec/db/models/__init__.py,sha256=xsER3dtbsj14mSb52v1Mf1bE0RzKbvrvwD6xti2RN5s,2132
9
+ constec/db/models/base.py,sha256=1t_eYXYyggoL5fKLcitRPKr1qUE1Ql3_sOmSHQMD3QE,359
10
+ constec/db/models/company.py,sha256=VZMt6gmsEWBL6VPUYzTPYOngBqL3mPYmZ6ZuCiz5Jeo,1055
11
+ constec/db/models/contact.py,sha256=qzkekZBNBtusFMpZRANxwKjtDN9qCxc4BMz-H7RyWM0,1904
12
+ constec/db/models/erp.py,sha256=sN0U0XCMNW-pc9CNLZG_kPZeWXznq_XU3ML2zJ3arjA,3518
13
+ constec/db/models/erp_entity.py,sha256=lJG0sU7YfehuxoqrxmpvuCSG0_GsardU5DvwBgMKsW8,3412
14
+ constec/db/models/flow.py,sha256=K_UA7E4zJ2-UPNp5H9e-lfPeGn0wDEiOl0cRal4Dmb8,4414
15
+ constec/db/models/group.py,sha256=ueCOIfpmzR683ojWf5vLb6IG_jawfVRM7IJcGK05I3Y,897
16
+ constec/db/models/module.py,sha256=na_7pJw8Q2D2UkF86ZwK5VdJnV87yqUOMBvaBLw4KgI,1218
17
+ constec/db/models/organization.py,sha256=9dbsh5UBShtDRAeyMqqQEPEaflhzS3aiZpRWUiEluBU,1731
18
+ constec/db/models/person.py,sha256=B4BNdy2AgqmxrZrkWWvqnFxFHs4uO1n61Y_5sP6MsUo,848
19
+ constec/db/models/session.py,sha256=HeIk5rK25IaxKBRX-XNuJ75ndjJZ08rAdSGTLvTwlLE,2695
20
+ constec/db/models/tag.py,sha256=wgJYoffXCO_-G7zhS1FLiQHFtJVbJKPV-wyRG3mwb74,1730
21
+ constec/db/models/user.py,sha256=UzCgb47ZVg1cBN_Y5CIpZJeHSC8JOAYT_0xZE3d294Y,1921
22
+ constec/services/__init__.py,sha256=LXGKIzaRf1gVG2y7oUc18EulUsJXHVs8-oz0nY2h7WA,312
23
+ constec/services/encryption.py,sha256=rwfAGUC7kDy1bhezHPoZG1hTjajpEB6l3AgcfGtmdvg,2368
24
+ constec/shared/__init__.py,sha256=Qe99OxBfwg3_4i4zyCQR20vCcyyaIULpLzUa0XfwBPg,435
25
+ constec/shared/exceptions.py,sha256=8Bih40RWoH0gVhto09mH2ppSQV_drHPnGWITcoD-0J0,1335
26
+ constec/utils/__init__.py,sha256=brf-G4UvU-3CK_rKNPTaHwsVsxnoJSbml_QTZJSM7d0,458
27
+ constec/utils/cuit.py,sha256=dQKGlA4pRQ5DyR-N4BiV8ZsvAle2Vgjif7PU72zHx_A,2753
28
+ constec/utils/password.py,sha256=XNpTJ9xZQSoZNjXEAnexAEZuYkwW1dFgX4AY-B5Q0gA,1462
29
+ constec-0.2.0.dist-info/licenses/LICENSE,sha256=a1R51ONDGq0UQfV-n3ybsNL7EGhcC2sQ1sXvRANaFVI,1064
30
+ constec-0.2.0.dist-info/METADATA,sha256=Qa43yajTCNkSvEodrX0OmBs6iy27qVrvR_LxE-e7CVU,2954
31
+ constec-0.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
32
+ constec-0.2.0.dist-info/top_level.txt,sha256=bQ9AydOLlthShsr7tA7t7ivbLvlLPdhHOo0BdWgnh_Y,8
33
+ constec-0.2.0.dist-info/RECORD,,