diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index b1a53f753..dec6250a6 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -4199,6 +4199,72 @@
- [X] update_policy
+## personalize
+
+6% implemented
+
+- [ ] create_batch_inference_job
+- [ ] create_batch_segment_job
+- [ ] create_campaign
+- [ ] create_dataset
+- [ ] create_dataset_export_job
+- [ ] create_dataset_group
+- [ ] create_dataset_import_job
+- [ ] create_event_tracker
+- [ ] create_filter
+- [ ] create_recommender
+- [X] create_schema
+- [ ] create_solution
+- [ ] create_solution_version
+- [ ] delete_campaign
+- [ ] delete_dataset
+- [ ] delete_dataset_group
+- [ ] delete_event_tracker
+- [ ] delete_filter
+- [ ] delete_recommender
+- [X] delete_schema
+- [ ] delete_solution
+- [ ] describe_algorithm
+- [ ] describe_batch_inference_job
+- [ ] describe_batch_segment_job
+- [ ] describe_campaign
+- [ ] describe_dataset
+- [ ] describe_dataset_export_job
+- [ ] describe_dataset_group
+- [ ] describe_dataset_import_job
+- [ ] describe_event_tracker
+- [ ] describe_feature_transformation
+- [ ] describe_filter
+- [ ] describe_recipe
+- [ ] describe_recommender
+- [X] describe_schema
+- [ ] describe_solution
+- [ ] describe_solution_version
+- [ ] get_solution_metrics
+- [ ] list_batch_inference_jobs
+- [ ] list_batch_segment_jobs
+- [ ] list_campaigns
+- [ ] list_dataset_export_jobs
+- [ ] list_dataset_groups
+- [ ] list_dataset_import_jobs
+- [ ] list_datasets
+- [ ] list_event_trackers
+- [ ] list_filters
+- [ ] list_recipes
+- [ ] list_recommenders
+- [X] list_schemas
+- [ ] list_solution_versions
+- [ ] list_solutions
+- [ ] list_tags_for_resource
+- [ ] start_recommender
+- [ ] stop_recommender
+- [ ] stop_solution_version_creation
+- [ ] tag_resource
+- [ ] untag_resource
+- [ ] update_campaign
+- [ ] update_recommender
+
+
## pinpoint
10% implemented
@@ -6268,7 +6334,6 @@
- opsworkscm
- outposts
- panorama
-- personalize
- personalize-events
- personalize-runtime
- pi
diff --git a/docs/docs/services/personalize.rst b/docs/docs/services/personalize.rst
new file mode 100644
index 000000000..dd615ace6
--- /dev/null
+++ b/docs/docs/services/personalize.rst
@@ -0,0 +1,94 @@
+.. _implementedservice_personalize:
+
+.. |start-h3| raw:: html
+
+
+
+.. |end-h3| raw:: html
+
+
+
+===========
+personalize
+===========
+
+.. autoclass:: moto.personalize.models.PersonalizeBackend
+
+|start-h3| Example usage |end-h3|
+
+.. sourcecode:: python
+
+ @mock_personalize
+ def test_personalize_behaviour:
+ boto3.client("personalize")
+ ...
+
+
+
+|start-h3| Implemented features for this service |end-h3|
+
+- [ ] create_batch_inference_job
+- [ ] create_batch_segment_job
+- [ ] create_campaign
+- [ ] create_dataset
+- [ ] create_dataset_export_job
+- [ ] create_dataset_group
+- [ ] create_dataset_import_job
+- [ ] create_event_tracker
+- [ ] create_filter
+- [ ] create_recommender
+- [X] create_schema
+- [ ] create_solution
+- [ ] create_solution_version
+- [ ] delete_campaign
+- [ ] delete_dataset
+- [ ] delete_dataset_group
+- [ ] delete_event_tracker
+- [ ] delete_filter
+- [ ] delete_recommender
+- [X] delete_schema
+- [ ] delete_solution
+- [ ] describe_algorithm
+- [ ] describe_batch_inference_job
+- [ ] describe_batch_segment_job
+- [ ] describe_campaign
+- [ ] describe_dataset
+- [ ] describe_dataset_export_job
+- [ ] describe_dataset_group
+- [ ] describe_dataset_import_job
+- [ ] describe_event_tracker
+- [ ] describe_feature_transformation
+- [ ] describe_filter
+- [ ] describe_recipe
+- [ ] describe_recommender
+- [X] describe_schema
+- [ ] describe_solution
+- [ ] describe_solution_version
+- [ ] get_solution_metrics
+- [ ] list_batch_inference_jobs
+- [ ] list_batch_segment_jobs
+- [ ] list_campaigns
+- [ ] list_dataset_export_jobs
+- [ ] list_dataset_groups
+- [ ] list_dataset_import_jobs
+- [ ] list_datasets
+- [ ] list_event_trackers
+- [ ] list_filters
+- [ ] list_recipes
+- [ ] list_recommenders
+- [X] list_schemas
+
+ Pagination is not yet implemented
+
+
+- [ ] list_solution_versions
+- [ ] list_solutions
+- [ ] list_tags_for_resource
+- [ ] start_recommender
+- [ ] stop_recommender
+- [ ] stop_solution_version_creation
+- [ ] tag_resource
+- [ ] untag_resource
+- [ ] update_campaign
+- [ ] update_recommender
+
diff --git a/moto/__init__.py b/moto/__init__.py
index 19dfaf7b2..c99b6f3b6 100644
--- a/moto/__init__.py
+++ b/moto/__init__.py
@@ -107,6 +107,7 @@ mock_mediastoredata = lazy_load(
mock_mq = lazy_load(".mq", "mock_mq", boto3_name="mq")
mock_opsworks = lazy_load(".opsworks", "mock_opsworks")
mock_organizations = lazy_load(".organizations", "mock_organizations")
+mock_personalize = lazy_load(".personalize", "mock_personalize")
mock_pinpoint = lazy_load(".pinpoint", "mock_pinpoint")
mock_polly = lazy_load(".polly", "mock_polly")
mock_quicksight = lazy_load(".quicksight", "mock_quicksight")
diff --git a/moto/backend_index.py b/moto/backend_index.py
index 4ff8408f4..975d24440 100644
--- a/moto/backend_index.py
+++ b/moto/backend_index.py
@@ -103,6 +103,7 @@ backend_url_patterns = [
("mq", re.compile("https?://mq\\.(.+)\\.amazonaws\\.com")),
("opsworks", re.compile("https?://opsworks\\.us-east-1\\.amazonaws.com")),
("organizations", re.compile("https?://organizations\\.(.+)\\.amazonaws\\.com")),
+ ("personalize", re.compile("https?://personalize\\.(.+)\\.amazonaws\\.com")),
("pinpoint", re.compile("https?://pinpoint\\.(.+)\\.amazonaws\\.com")),
("polly", re.compile("https?://polly\\.(.+)\\.amazonaws.com")),
("quicksight", re.compile("https?://quicksight\\.(.+)\\.amazonaws\\.com")),
diff --git a/moto/personalize/__init__.py b/moto/personalize/__init__.py
new file mode 100644
index 000000000..fe2fdaa2a
--- /dev/null
+++ b/moto/personalize/__init__.py
@@ -0,0 +1,5 @@
+"""personalize module initialization; sets value for base decorator."""
+from .models import personalize_backends
+from ..core.models import base_decorator
+
+mock_personalize = base_decorator(personalize_backends)
diff --git a/moto/personalize/exceptions.py b/moto/personalize/exceptions.py
new file mode 100644
index 000000000..68259f691
--- /dev/null
+++ b/moto/personalize/exceptions.py
@@ -0,0 +1,13 @@
+"""Exceptions raised by the personalize service."""
+from moto.core.exceptions import JsonRESTError
+
+
+class PersonalizeException(JsonRESTError):
+ pass
+
+
+class ResourceNotFoundException(PersonalizeException):
+ def __init__(self, arn):
+ super().__init__(
+ "ResourceNotFoundException", f"Resource Arn {arn} does not exist."
+ )
diff --git a/moto/personalize/models.py b/moto/personalize/models.py
new file mode 100644
index 000000000..6a44257cf
--- /dev/null
+++ b/moto/personalize/models.py
@@ -0,0 +1,64 @@
+"""PersonalizeBackend class with methods for supported APIs."""
+
+from .exceptions import ResourceNotFoundException
+from moto.core import BaseBackend, BaseModel
+from moto.core.utils import BackendDict, unix_time
+
+
+class Schema(BaseModel):
+ def __init__(self, account_id, region, name, schema, domain):
+ self.name = name
+ self.schema = schema
+ self.domain = domain
+ self.arn = f"arn:aws:personalize:{region}:{account_id}:schema/{name}"
+ self.created = unix_time()
+
+ def to_dict(self, full=True):
+ d = {
+ "name": self.name,
+ "schemaArn": self.arn,
+ "domain": self.domain,
+ "creationDateTime": self.created,
+ "lastUpdatedDateTime": self.created,
+ }
+ if full:
+ d["schema"] = self.schema
+ return d
+
+
+class PersonalizeBackend(BaseBackend):
+ """Implementation of Personalize APIs."""
+
+ def __init__(self, region_name, account_id):
+ super().__init__(region_name, account_id)
+ self.schemas: [str, Schema] = dict()
+
+ def create_schema(self, name, schema, domain):
+ schema = Schema(
+ region=self.region_name,
+ account_id=self.account_id,
+ name=name,
+ schema=schema,
+ domain=domain,
+ )
+ self.schemas[schema.arn] = schema
+ return schema.arn
+
+ def delete_schema(self, schema_arn):
+ if schema_arn not in self.schemas:
+ raise ResourceNotFoundException(schema_arn)
+ self.schemas.pop(schema_arn, None)
+
+ def describe_schema(self, schema_arn):
+ if schema_arn not in self.schemas:
+ raise ResourceNotFoundException(schema_arn)
+ return self.schemas[schema_arn]
+
+ def list_schemas(self) -> [Schema]:
+ """
+ Pagination is not yet implemented
+ """
+ return self.schemas.values()
+
+
+personalize_backends = BackendDict(PersonalizeBackend, "personalize")
diff --git a/moto/personalize/responses.py b/moto/personalize/responses.py
new file mode 100644
index 000000000..ac100b799
--- /dev/null
+++ b/moto/personalize/responses.py
@@ -0,0 +1,48 @@
+"""Handles incoming personalize requests, invokes methods, returns responses."""
+import json
+
+from moto.core.responses import BaseResponse
+from .models import personalize_backends
+
+
+class PersonalizeResponse(BaseResponse):
+ """Handler for Personalize requests and responses."""
+
+ def __init__(self):
+ super().__init__(service_name="personalize")
+
+ @property
+ def personalize_backend(self):
+ """Return backend instance specific for this region."""
+ return personalize_backends[self.current_account][self.region]
+
+ # add methods from here
+
+ def create_schema(self):
+ params = json.loads(self.body)
+ name = params.get("name")
+ schema = params.get("schema")
+ domain = params.get("domain")
+ schema_arn = self.personalize_backend.create_schema(
+ name=name,
+ schema=schema,
+ domain=domain,
+ )
+ return json.dumps(dict(schemaArn=schema_arn))
+
+ def delete_schema(self):
+ params = json.loads(self.body)
+ schema_arn = params.get("schemaArn")
+ self.personalize_backend.delete_schema(schema_arn=schema_arn)
+ return "{}"
+
+ def describe_schema(self):
+ params = json.loads(self.body)
+ schema_arn = params.get("schemaArn")
+ schema = self.personalize_backend.describe_schema(schema_arn=schema_arn)
+ return json.dumps(dict(schema=schema.to_dict()))
+
+ def list_schemas(self):
+ schemas = self.personalize_backend.list_schemas()
+ resp = {"schemas": [s.to_dict(full=False) for s in schemas]}
+ return json.dumps(resp)
diff --git a/moto/personalize/urls.py b/moto/personalize/urls.py
new file mode 100644
index 000000000..731bcdcab
--- /dev/null
+++ b/moto/personalize/urls.py
@@ -0,0 +1,11 @@
+"""personalize base URL and path."""
+from .responses import PersonalizeResponse
+
+url_bases = [
+ r"https?://personalize\.(.+)\.amazonaws\.com",
+]
+
+
+url_paths = {
+ "{0}/$": PersonalizeResponse.dispatch,
+}
diff --git a/tests/test_personalize/__init__.py b/tests/test_personalize/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/test_personalize/test_personalize_schema.py b/tests/test_personalize/test_personalize_schema.py
new file mode 100644
index 000000000..29912e18f
--- /dev/null
+++ b/tests/test_personalize/test_personalize_schema.py
@@ -0,0 +1,121 @@
+"""Unit tests for personalize-supported APIs."""
+import boto3
+import json
+import sure # noqa # pylint: disable=unused-import
+import pytest
+from botocore.exceptions import ClientError
+from moto import mock_personalize
+from moto.core import DEFAULT_ACCOUNT_ID
+
+# See our Development Tips on writing tests for hints on how to write good tests:
+# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
+
+
+@mock_personalize
+def test_create_schema():
+ client = boto3.client("personalize", region_name="ap-southeast-1")
+ schema = {
+ "type": "record",
+ "name": "Interactions",
+ "namespace": "com.amazonaws.personalize.schema",
+ "fields": [
+ {"name": "USER_ID", "type": "string"},
+ {"name": "ITEM_ID", "type": "string"},
+ {"name": "TIMESTAMP", "type": "long"},
+ ],
+ "version": "1.0",
+ }
+
+ create_schema_response = client.create_schema(
+ name="personalize-demo-schema", schema=json.dumps(schema)
+ )
+ create_schema_response.should.have.key("schemaArn").equals(
+ f"arn:aws:personalize:ap-southeast-1:{DEFAULT_ACCOUNT_ID}:schema/personalize-demo-schema"
+ )
+
+
+@mock_personalize
+def test_delete_schema():
+ client = boto3.client("personalize", region_name="us-east-1")
+ schema_arn = client.create_schema(name="myname", schema=json.dumps("sth"))[
+ "schemaArn"
+ ]
+ client.delete_schema(schemaArn=schema_arn)
+
+ client.list_schemas().should.have.key("schemas").equals([])
+
+
+@mock_personalize
+def test_delete_schema__unknown():
+ arn = f"arn:aws:personalize:ap-southeast-1:{DEFAULT_ACCOUNT_ID}:schema/personalize-demo-schema"
+ client = boto3.client("personalize", region_name="us-east-2")
+ with pytest.raises(ClientError) as exc:
+ client.delete_schema(schemaArn=arn)
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("ResourceNotFoundException")
+ err["Message"].should.equal(f"Resource Arn {arn} does not exist.")
+
+
+@mock_personalize
+def test_describe_schema():
+ client = boto3.client("personalize", region_name="us-east-2")
+ schema_arn = client.create_schema(name="myname", schema="sth")["schemaArn"]
+ resp = client.describe_schema(schemaArn=schema_arn)
+ resp.should.have.key("schema")
+ schema = resp["schema"]
+
+ schema.should.have.key("name").equals("myname")
+ schema.should.have.key("schemaArn").match("schema/myname")
+ schema.should.have.key("schema").equals("sth")
+ schema.should.have.key("creationDateTime")
+ schema.should.have.key("lastUpdatedDateTime")
+
+
+@mock_personalize
+def test_describe_schema__with_domain():
+ client = boto3.client("personalize", region_name="us-east-2")
+ schema_arn = client.create_schema(name="myname", schema="sth", domain="ECOMMERCE")[
+ "schemaArn"
+ ]
+ resp = client.describe_schema(schemaArn=schema_arn)
+ resp.should.have.key("schema")
+ schema = resp["schema"]
+
+ schema.should.have.key("domain").equals("ECOMMERCE")
+
+
+@mock_personalize
+def test_describe_schema__unknown():
+ arn = (
+ "arn:aws:personalize:ap-southeast-1:486285699788:schema/personalize-demo-schema"
+ )
+ client = boto3.client("personalize", region_name="us-east-2")
+ with pytest.raises(ClientError) as exc:
+ client.describe_schema(schemaArn=arn)
+ err = exc.value.response["Error"]
+ err["Code"].should.equal("ResourceNotFoundException")
+ err["Message"].should.equal(f"Resource Arn {arn} does not exist.")
+
+
+@mock_personalize
+def test_list_schemas__initial():
+ client = boto3.client("personalize", region_name="us-east-2")
+ resp = client.list_schemas()
+
+ resp.should.have.key("schemas").equals([])
+
+
+@mock_personalize
+def test_list_schema():
+ client = boto3.client("personalize", region_name="us-east-2")
+ schema_arn = client.create_schema(name="myname", schema="sth")["schemaArn"]
+
+ resp = client.list_schemas()
+ resp.should.have.key("schemas").length_of(1)
+ schema = resp["schemas"][0]
+
+ schema.should.have.key("name").equals("myname")
+ schema.should.have.key("schemaArn").equals(schema_arn)
+ schema.shouldnt.have.key("schema")
+ schema.should.have.key("creationDateTime")
+ schema.should.have.key("lastUpdatedDateTime")